/* boost random/uniform_int.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * 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)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id$
 *
 * Revision history
 *  2001-04-08  added min<max assertion (N. Becker)
 *  2001-02-18  moved to individual header files
 */

#ifndef BOOST_RANDOM_UNIFORM_INT_HPP
#define BOOST_RANDOM_UNIFORM_INT_HPP

#include <boost/assert.hpp>
#include <boost/random/uniform_int_distribution.hpp>

namespace boost {

/**
 * The distribution function uniform_int models a \random_distribution.
 * On each invocation, it returns a random integer value uniformly
 * distributed in the set of integer numbers {min, min+1, min+2, ..., max}.
 *
 * The template parameter IntType shall denote an integer-like value type.
 *
 * This class is deprecated.  Please use @c uniform_int_distribution in
 * new code.
 */
template<class IntType = int>
class uniform_int : public random::uniform_int_distribution<IntType>
{
    typedef random::uniform_int_distribution<IntType> base_type;
public:

    class param_type : public base_type::param_type
    {
    public:
        typedef uniform_int distribution_type;
        /**
         * Constructs the parameters of a uniform_int distribution.
         *
         * Requires: min <= max
         */
        explicit param_type(IntType min_arg = 0, IntType max_arg = 9)
          : base_type::param_type(min_arg, max_arg)
        {}
    };

    /**
     * Constructs a uniform_int object. @c min and @c max are
     * the parameters of the distribution.
     *
     * Requires: min <= max
     */
    explicit uniform_int(IntType min_arg = 0, IntType max_arg = 9)
      : base_type(min_arg, max_arg)
    {}

    /** Constructs a uniform_int distribution from its parameters. */
    explicit uniform_int(const param_type& parm)
      : base_type(parm)
    {}

    /** Returns the parameters of the distribution */
    param_type param() const { return param_type(this->a(), this->b()); }
    /** Sets the parameters of the distribution. */
    void param(const param_type& parm) { this->base_type::param(parm); }

    // Codergear seems to have trouble with a using declaration here

    template<class Engine>
    IntType operator()(Engine& eng) const
    {
        return static_cast<const base_type&>(*this)(eng);
    }

    template<class Engine>
    IntType operator()(Engine& eng, const param_type& parm) const
    {
        return static_cast<const base_type&>(*this)(eng, parm);
    }

    template<class Engine>
    IntType operator()(Engine& eng, IntType n) const
    {
        BOOST_ASSERT(n > 0);
        return static_cast<const base_type&>(*this)(eng, param_type(0, n - 1));
    }
};

} // namespace boost

#endif // BOOST_RANDOM_UNIFORM_INT_HPP


#include <boost/simd/meta/is_aligned.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/int.hpp>

int main()
{
  using boost::mpl::int_;

  BOOST_MPL_ASSERT    (( boost::simd::meta::is_aligned< int_<0>,int_<2> >::type   ));
  BOOST_MPL_ASSERT    (( boost::simd::meta::is_aligned< int_<4>,int_<2> >::type   ));
  BOOST_MPL_ASSERT_NOT(( boost::simd::meta::is_aligned< int_<17>,int_<2> >::type  ));
}


/*--------------------------------------------------------------------------*\
 |                                                                          |
 |  This program is free software; you can redistribute it and/or modify    |
 |  it under the terms of the GNU General Public License as published by    |
 |  the Free Software Foundation; either version 2, or (at your option)     |
 |  any later version.                                                      |
 |                                                                          |
 |  This program is distributed in the hope that it will be useful,         |
 |  but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 |  GNU General Public License for more details.                            |
 |                                                                          |
 |  You should have received a copy of the GNU General Public License       |
 |  along with this program; if not, write to the Free Software             |
 |  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               |
 |                                                                          |
 |  Copyright (C) 2003                                                      |
 |                                                                          |
 |         , __                 , __                                        |
 |        /|/  \               /|/  \                                       |
 |         | __/ _   ,_         | __/ _   ,_                                |
 |         |   \|/  /  |  |   | |   \|/  /  |  |   |                        |
 |         |(__/|__/   |_/ \_/|/|(__/|__/   |_/ \_/|/                       |
 |                           /|                   /|                        |
 |                           \|                   \|                        |
 |                                                                          |
 |      Enrico Bertolazzi                                                   |
 |      Dipartimento di Ingegneria Meccanica e Strutturale                  |
 |      Universita` degli Studi di Trento                                   |
 |      Via Mesiano 77, I-38050 Trento, Italy                               |
 |      email: enrico.bertolazzi@unitn.it                                   |
 |                                                                          |
\*--------------------------------------------------------------------------*/

/*
  http://www.sfu.ca/~ssurjano/optimization.html
  http://www-optima.amp.i.kyoto-u.ac.jp/member/student/hedar/Hedar_files/TestGO_files/Page364.htm
*/

#ifndef TESTS_NONLIN_HH
#define TESTS_NONLIN_HH

#include <iostream>
#include <sstream>

#include <string>
#include <vector>
#include <cstdint>
#include <cstdio>
#include <cmath>
#include <map>

#define DEBUG 1
#define EIGEN_NO_AUTOMATIC_RESIZING 1

#include <Utils.hh>
#include <Eigen/Core>
#include <Eigen/Dense>

namespace NLproblem {

  typedef std::basic_ostream<char> ostream_type;

  using std::string;
  using std::map;
  using std::vector;
  using std::pair;
  using std::max;
  using std::min;

  using std::numeric_limits;

  typedef double  real_type;
  typedef int32_t integer;

  //! `m_e` the value of \f$ e \f$.
  static real_type const m_e = 2.718281828459045235360287471352662497757;

  //! `m_pi` the value of \f$ \pi \f$.
  static real_type const m_pi = 3.141592653589793238462643383279502884197;

  //! `m_2pi` the value of \f$ 2\pi \f$.
  static real_type const m_2pi = 6.283185307179586476925286766559005768394;

  //! `m_pi_2` the value of \f$ \pi/2 \f$.
  static real_type const m_pi_2 = 1.570796326794896619231321691639751442098;

  //! `m_pi_4` the value of \f$ \pi/4 \f$.
  static real_type const m_pi_4 = 0.7853981633974483096156608458198757210492;

  //! `m_1_pi` the value of \f$ 1/\pi \f$.
  static real_type const m_1_pi = 0.3183098861837906715377675267450287240689;

  //! `m_2_pi` the value of \f$ 2/\pi \f$.
  static real_type const m_2_pi = 0.6366197723675813430755350534900574481378;

  //! `m_sqrtpi` the value of \f$ \sqrt{\pi} \f$.
  static real_type const m_sqrtpi = 1.772453850905516027298167483341145182798;

  //! `m_2_sqrtpi` the value of \f$ 2/\sqrt{\pi} \f$.
  static real_type const m_2_sqrtpi = 1.128379167095512573896158903121545171688;

  //! `m_sqrt2` the value of \f$ \sqrt{2} \f$.
  static real_type const m_sqrt2 = 1.414213562373095048801688724209698078570;

  //! `m_1_sqrt2` the value of \f$ 1/\sqrt{2} \f$.
  static real_type const m_1_sqrt2 = 0.7071067811865475244008443621048490392850;

  static real_type real_max = numeric_limits<real_type>::max();

  typedef Eigen::Matrix<real_type,Eigen::Dynamic,Eigen::Dynamic> dmat_t;
  typedef Eigen::Matrix<real_type,Eigen::Dynamic,1>              dvec_t;
  typedef Eigen::Matrix<integer,Eigen::Dynamic,1>               ivec_t;

  class nonlinearBase {

    string const _title;
    string const _bibtex;

    nonlinearBase();
    nonlinearBase( nonlinearBase const & );
    nonlinearBase const & operator = ( nonlinearBase const & );

  protected:

    // U T I L I T Y  F U N C T I O N S -----------------------------------------

    real_type power2( real_type a ) const { return a*a; }
    real_type power3( real_type a ) const { return a*a*a; }
    real_type power4( real_type a ) const { real_type a2 = a*a; return a2*a2; }
    real_type power5( real_type a ) const { real_type a2 = a*a; return a2*a2*a; }
    real_type power6( real_type a ) const { real_type a2 = a*a; return a2*a2*a2; }

    void
    checkMinEquations( integer i, integer i_min ) const {
      UTILS_ASSERT(
        i >= i_min, "checkMinEquations:: i = {} < {}", i, i_min
      );
    }

    void
    checkEven( integer i, integer i_min ) const {
      UTILS_ASSERT(
        (i % 2) == 0 && i >= i_min, "checkEven:: odd index i = {}", i
      );
    }

    void
    checkOdd( integer i, integer i_min ) const {
      UTILS_ASSERT(
        (i % 2) != 0 && i >= i_min, "checkOdd:: odd index i = {}", i
      );
    }

    void
    checkThree( integer i, integer i_min ) const {
      UTILS_ASSERT(
        (i % 3) == 0 && i >= i_min, "checkThree:: index i = {}", i
      );
    }

    void
    checkFour( integer i, integer i_min ) const {
      UTILS_ASSERT(
        (i % 4) == 0 && i >= i_min, "checkFour:: index i = {}", i
      );
    }

  public:

    explicit nonlinearBase( string const & t, string const & b )
    : _title( t )
    , _bibtex( b )
    {}

    virtual ~nonlinearBase() {}

    string const & bibtex() const { return _bibtex; }
    string const & title()  const { return _title; }

  };

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  class multivariateFunction : public nonlinearBase {

    multivariateFunction( multivariateFunction const & );
    multivariateFunction const & operator = ( multivariateFunction const & );

  protected:
    integer n;

  public:

    multivariateFunction( string const & t, string const & b, integer _n )
    : nonlinearBase( fmt::format("{} neq = {}", t, _n ), b )
    , n(_n)
    { }

    virtual ~multivariateFunction() {}

    virtual real_type eval( dvec_t const & x ) const = 0;
    virtual void      gradient( dvec_t const & x, dvec_t & g ) const = 0;

    virtual integer hessianNnz() const = 0;
    virtual void    hessian( dvec_t const & x, dvec_t & jac ) const = 0;
    virtual void    hessianPattern( ivec_t & i, ivec_t & j ) const = 0;

    virtual integer numExactSolution() const = 0;
    virtual void    getExactSolution( dvec_t & x, integer idx ) const = 0;

    virtual integer numInitialPoint() const = 0;
    virtual void    getInitialPoint( dvec_t & x, integer idx ) const = 0;

    virtual void    checkIfAdmissible( dvec_t const & x ) const = 0;

    virtual void
    boundingBox( dvec_t & L, dvec_t & U ) const {
      L.fill( -real_max );
      U.fill( real_max );
    }

    integer dimX( void ) const { return n; }

  };

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  class nonlinearLeastSquares: public nonlinearBase {

    nonlinearLeastSquares( nonlinearLeastSquares const & );
    nonlinearLeastSquares const & operator = ( nonlinearLeastSquares const & );

  protected:

    integer n, m;

  public:

    nonlinearLeastSquares(
      string const & t,
      string const & b,
      integer       _n,
      integer       _m
    )
    : nonlinearBase(
        fmt::format( "{} dimF = {}, dimX = {}", t, _n, _m ), b
      )
    , n(_n)
    , m(_m)
    {}

    virtual ~nonlinearLeastSquares() {}

    virtual real_type evalFk( dvec_t const & x, integer k ) const = 0;
    virtual void      evalF ( dvec_t const & x, dvec_t & f ) const = 0;

    virtual integer jacobianNnz() const = 0;
    virtual void    jacobian( dvec_t const & x, dvec_t & jac ) const = 0;
    virtual void    jacobianPattern( ivec_t & i, ivec_t & j ) const = 0;

    virtual integer tensorNnz() const = 0;
    virtual void    tensor( dvec_t const & x, dvec_t const & lambda, dvec_t & jac ) const = 0;
    virtual void    tensorPattern( ivec_t & i, ivec_t & j ) const = 0;

    virtual integer numExactSolution() const = 0;
    virtual void    getExactSolution( dvec_t & x, integer idx ) const = 0;

    virtual integer numInitialPoint() const = 0;
    virtual void    getInitialPoint( dvec_t & x, integer idx ) const = 0;

    virtual void checkIfAdmissible( dvec_t const & x ) const = 0;

    virtual void
    boundingBox( dvec_t & L, dvec_t & U ) const {
      L.fill( -real_max );
      U.fill( real_max );
    }

    integer dimF( void ) const { return n; }
    integer dimX( void ) const { return m; }

  };

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  class nonlinearSystem: public nonlinearBase {

    nonlinearSystem( nonlinearSystem const & );
    nonlinearSystem const & operator = ( nonlinearSystem const & );

  protected:

    // U T I L I T Y  F U N C T I O N S -----------------------------------------

    // n = dimensione matrice
    // i = riga
    // j = colonna
    // indirizzamento fortran
    integer faddr( integer i, integer j ) const
    { return (i-1) + (j-1) * n; }

    integer caddr( integer i, integer j ) const
    { return i + j * n; }

    integer n;

  public:

    nonlinearSystem( string const & t, string const & b, integer _n )
    : nonlinearBase( fmt::format("{} neq = {}", t, _n ), b )
    , n(_n)
    { }

    virtual ~nonlinearSystem() {}

    //void
    //setup( string const & t, integer _n )
    //{ theTitle = t; n = _n; }

    virtual real_type evalFk( dvec_t const & x, integer k ) const = 0;
    virtual void      evalF ( dvec_t const & x, dvec_t & f ) const = 0;

    virtual integer jacobianNnz() const = 0;
    virtual void    jacobian( dvec_t const & x, dvec_t & jac ) const = 0;
    virtual void    jacobianPattern( ivec_t & i, ivec_t & j ) const = 0;

    virtual integer numExactSolution() const = 0;
    virtual void    getExactSolution( dvec_t & x, integer idx ) const = 0;

    virtual integer numInitialPoint() const = 0;
    virtual void    getInitialPoint( dvec_t & x, integer idx ) const = 0;

    virtual void checkIfAdmissible( dvec_t const & x ) const = 0;

    virtual void
    boundingBox( dvec_t & L, dvec_t & U ) const {
      L.fill( -real_max );
      U.fill( real_max );
    }

    integer numEqns( void ) const { return n; }

    integer
    fill_CSR(
      dvec_t const & x,
      ivec_t       & R,
      ivec_t       & J,
      dvec_t       & values
    ) const;

  };

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  class nonlinearSystemFromMultivariateFunction: public nonlinearSystem {

    nonlinearSystemFromMultivariateFunction(
      nonlinearSystemFromMultivariateFunction const &
    );
    nonlinearSystemFromMultivariateFunction const &
    operator = (nonlinearSystemFromMultivariateFunction const &);

    multivariateFunction const * pMF;

  public:

    nonlinearSystemFromMultivariateFunction(
      multivariateFunction const * _pMF
    )
    : nonlinearSystem( _pMF->title(), _pMF->bibtex(), _pMF->dimX() )
    , pMF(_pMF) {
    }

    virtual ~nonlinearSystemFromMultivariateFunction() {}

    virtual
    real_type
    evalFk( dvec_t const & x, integer k ) const {
      dvec_t g(n);
      evalF( x, g );
      return g(k);
    }

    virtual
    void
    evalF( dvec_t const & x, dvec_t & g ) const {
      pMF->gradient( x, g );
    }

    virtual
    integer
    jacobianNnz() const
    { return pMF->hessianNnz(); }

    virtual
    void
    jacobian( dvec_t const & x, dvec_t & hess ) const
    { pMF->hessian(x,hess); }

    virtual
    void
    jacobianPattern( ivec_t & i, ivec_t & j ) const
    { pMF->hessianPattern( i, j ); }

    virtual
    integer
    numExactSolution() const
    { return pMF->numExactSolution(); }

    virtual
    void
    getExactSolution( dvec_t & x, integer idx ) const
    { pMF->getExactSolution( x, idx ); }

    virtual
    integer
    numInitialPoint() const
    { return pMF->numInitialPoint(); }

    virtual
    void
    getInitialPoint( dvec_t & x, integer idx ) const
    { pMF->getInitialPoint( x, idx ); }

    virtual
    void
    checkIfAdmissible( dvec_t const & x ) const
    { pMF->checkIfAdmissible( x ); }

    virtual
    void
    boundingBox( dvec_t & L, dvec_t & U ) const
    { pMF->boundingBox( L, U ); }

    integer numEqns( void ) const { return pMF->dimX(); }

    string const & title(void) const { return pMF->title(); }

  };

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#if 0
  class nonlinearSystemFromLeastSquares: public nonlinearSystem {

    nonlinearSystemFromLeastSquares(nonlinearSystemFromLeastSquares const &);
    nonlinearSystemFromLeastSquares const &
    operator = (nonlinearSystemFromLeastSquares const &);

    nonlinearLeastSquares const * pLS;

  public:

    nonlinearSystemFromLeastSquares( nonlinearLeastSquares const * _pLS )
    : nonlinearSystem( _pLS->title(), _pLS->dimX() )
    , pLS(_pLS) {
    }

    virtual ~nonlinearSystemFromLeastSquares() {}

    virtual
    real_type
    evalFk( dvec_t const & x, integer k ) const {
      dvec_t g(n);
      evalF( x, g );
      return g(k);
    }

    virtual
    void
    evalF( dvec_t const & x, dvec_t & g ) const {
      pLS->gradient( x, g );
    }

    virtual
    integer
    jacobianNnz() const
    { return pMF->hessianNnz(); }

    virtual
    void
    jacobian( dvec_t const & x, dmat_t & hess ) const
    { pMF->hessian(x,hess); }

    virtual
    void
    jacobianPattern( ivec_t & i, ivec_t & j ) const
    { pMF->hessianPattern(i,j); }

    virtual
    integer
    numExactSolution() const
    { return pMF->numExactSolution(); }

    virtual
    void
    getExactSolution( dvec_t & x, integer idx ) const
    { pMF->getExactSolution(x,idx); }

    virtual
    integer
    numInitialPoint() const
    { return pMF->numInitialPoint(); }

    virtual
    void
    getInitialPoint( dvec_t & x, integer idx ) const
    { pMF->getInitialPoint(x,idx); }

    virtual
    void
    checkIfAdmissible( dvec_t const & x ) const
    { pMF->checkIfAdmissible(x); }

    virtual
    void
    boundingBox( dvec_t & L, dvec_t & U ) const
    { pMF->boundingBox(L,U); }

    integer numEqns( void ) const { return pMF->dimX(); }

    string const & title(void) const { return pMF->title(); }

  };
#endif

  extern vector<nonlinearSystem*> theProblems;
  extern map<string,integer>      theProblemsMap;
  void initProblems();

}

#endif


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

#define BOOST_TEST_MODULE FrovedisTest
#include <boost/test/unit_test.hpp>

using namespace frovedis;
using namespace std;

std::vector<std::string> splitter(const std::string& line) {
  std::vector<std::string> wordvec;
  boost::split(wordvec, line, boost::is_space());
  return wordvec;
}

std::pair<std::string, int> counter(const std::string& v) {
  return std::make_pair(v, 1);
}

int sum(int a, int b){return a + b;}

BOOST_AUTO_TEST_CASE( frovedis_test )
{
  int argc = 1;
  char** argv = NULL;
  use_frovedis use(argc, argv);

  // testing for string counter
  auto d1 = frovedis::make_dvector_loadline("./sample.txt");
  auto d2 = d1.flat_map(splitter).map(counter).reduce_by_key<std::string,int>(sum);
  auto r = d2.as_dvector().sort().gather();

  // filling expected output vector
  std::vector<std::pair<std::string,int>> ref_out;
  ref_out.push_back(std::make_pair("Is", 2));
  ref_out.push_back(std::make_pair("Yes", 2));
  ref_out.push_back(std::make_pair("a", 3));
  ref_out.push_back(std::make_pair("is", 1));
  ref_out.push_back(std::make_pair("is!", 1));
  ref_out.push_back(std::make_pair("it", 1));
  ref_out.push_back(std::make_pair("really", 1));
  ref_out.push_back(std::make_pair("test.", 1));
  ref_out.push_back(std::make_pair("test?", 2));
  ref_out.push_back(std::make_pair("this", 3));

  BOOST_CHECK(r == ref_out);
  //for(auto& i: r) std::cout << i.first << ": " << i.second << std::endl;
}


//=======================================================================
// Copyright 2013 University of Warsaw.
// Authors: Piotr Wygocki
//
// 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)
//=======================================================================
//
// This algorithm is described in "Network Flows: Theory, Algorithms, and
// Applications"
// by Ahuja, Magnanti, Orlin.

#ifndef BOOST_GRAPH_SUCCESSIVE_SHORTEST_PATH_HPP
#define BOOST_GRAPH_SUCCESSIVE_SHORTEST_PATH_HPP

#include <numeric>

#include <boost/property_map/property_map.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/pending/indirect_cmp.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/graph/detail/augment.hpp>

namespace boost
{

namespace detail
{

    template < class Graph, class Weight, class Distance, class Reversed >
    class MapReducedWeight
    : public put_get_helper< typename property_traits< Weight >::value_type,
          MapReducedWeight< Graph, Weight, Distance, Reversed > >
    {
        typedef graph_traits< Graph > gtraits;

    public:
        typedef boost::readable_property_map_tag category;
        typedef typename property_traits< Weight >::value_type value_type;
        typedef value_type reference;
        typedef typename gtraits::edge_descriptor key_type;
        MapReducedWeight(const Graph& g, Weight w, Distance d, Reversed r)
        : g_(g), weight_(w), distance_(d), rev_(r)
        {
        }

        reference operator[](key_type v) const
        {
            return get(distance_, source(v, g_)) - get(distance_, target(v, g_))
                + get(weight_, v);
        }

    private:
        const Graph& g_;
        Weight weight_;
        Distance distance_;
        Reversed rev_;
    };

    template < class Graph, class Weight, class Distance, class Reversed >
    MapReducedWeight< Graph, Weight, Distance, Reversed > make_mapReducedWeight(
        const Graph& g, Weight w, Distance d, Reversed r)
    {
        return MapReducedWeight< Graph, Weight, Distance, Reversed >(
            g, w, d, r);
    }

} // detail

template < class Graph, class Capacity, class ResidualCapacity, class Reversed,
    class Pred, class Weight, class Distance, class Distance2,
    class VertexIndex >
void successive_shortest_path_nonnegative_weights(const Graph& g,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
    ResidualCapacity residual_capacity, Weight weight, Reversed rev,
    VertexIndex index, Pred pred, Distance distance, Distance2 distance_prev)
{
    filtered_graph< const Graph, is_residual_edge< ResidualCapacity > > gres
        = detail::residual_graph(g, residual_capacity);
    typedef typename graph_traits< Graph >::edge_descriptor edge_descriptor;

    BGL_FORALL_EDGES_T(e, g, Graph)
    {
        put(residual_capacity, e, get(capacity, e));
    }

    BGL_FORALL_VERTICES_T(v, g, Graph) { put(distance_prev, v, 0); }

    while (true)
    {
        BGL_FORALL_VERTICES_T(v, g, Graph) { put(pred, v, edge_descriptor()); }
        dijkstra_shortest_paths(gres, s,
            weight_map(
                detail::make_mapReducedWeight(gres, weight, distance_prev, rev))
                .distance_map(distance)
                .vertex_index_map(index)
                .visitor(make_dijkstra_visitor(
                    record_edge_predecessors(pred, on_edge_relaxed()))));

        if (get(pred, t) == edge_descriptor())
        {
            break;
        }

        BGL_FORALL_VERTICES_T(v, g, Graph)
        {
            put(distance_prev, v, get(distance_prev, v) + get(distance, v));
        }

        detail::augment(g, s, t, pred, residual_capacity, rev);
    }
}

// in this namespace argument dispatching tak place
namespace detail
{

    template < class Graph, class Capacity, class ResidualCapacity,
        class Weight, class Reversed, class Pred, class Distance,
        class Distance2, class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch3(const Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, Pred pred, Distance dist, Distance2 dist_pred)
    {
        successive_shortest_path_nonnegative_weights(g, s, t, capacity,
            residual_capacity, weight, rev, index, pred, dist, dist_pred);
    }

    // setting default distance map
    template < class Graph, class Capacity, class ResidualCapacity,
        class Weight, class Reversed, class Pred, class Distance,
        class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch3(Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, Pred pred, Distance dist, param_not_found)
    {
        typedef typename property_traits< Weight >::value_type D;

        std::vector< D > d_map(num_vertices(g));

        successive_shortest_path_nonnegative_weights(g, s, t, capacity,
            residual_capacity, weight, rev, index, pred, dist,
            make_iterator_property_map(d_map.begin(), index));
    }

    template < class Graph, class P, class T, class R, class Capacity,
        class ResidualCapacity, class Weight, class Reversed, class Pred,
        class Distance, class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch2(Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, Pred pred, Distance dist,
        const bgl_named_params< P, T, R >& params)
    {
        successive_shortest_path_nonnegative_weights_dispatch3(g, s, t,
            capacity, residual_capacity, weight, rev, index, pred, dist,
            get_param(params, vertex_distance2));
    }

    // setting default distance map
    template < class Graph, class P, class T, class R, class Capacity,
        class ResidualCapacity, class Weight, class Reversed, class Pred,
        class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch2(Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, Pred pred, param_not_found,
        const bgl_named_params< P, T, R >& params)
    {
        typedef typename property_traits< Weight >::value_type D;

        std::vector< D > d_map(num_vertices(g));

        successive_shortest_path_nonnegative_weights_dispatch3(g, s, t,
            capacity, residual_capacity, weight, rev, index, pred,
            make_iterator_property_map(d_map.begin(), index),
            get_param(params, vertex_distance2));
    }

    template < class Graph, class P, class T, class R, class Capacity,
        class ResidualCapacity, class Weight, class Reversed, class Pred,
        class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch1(Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, Pred pred, const bgl_named_params< P, T, R >& params)
    {
        successive_shortest_path_nonnegative_weights_dispatch2(g, s, t,
            capacity, residual_capacity, weight, rev, index, pred,
            get_param(params, vertex_distance), params);
    }

    // setting default predecessors map
    template < class Graph, class P, class T, class R, class Capacity,
        class ResidualCapacity, class Weight, class Reversed,
        class VertexIndex >
    void successive_shortest_path_nonnegative_weights_dispatch1(Graph& g,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t, Capacity capacity,
        ResidualCapacity residual_capacity, Weight weight, Reversed rev,
        VertexIndex index, param_not_found,
        const bgl_named_params< P, T, R >& params)
    {
        typedef typename graph_traits< Graph >::edge_descriptor edge_descriptor;
        std::vector< edge_descriptor > pred_vec(num_vertices(g));

        successive_shortest_path_nonnegative_weights_dispatch2(g, s, t,
            capacity, residual_capacity, weight, rev, index,
            make_iterator_property_map(pred_vec.begin(), index),
            get_param(params, vertex_distance), params);
    }

} // detail

template < class Graph, class P, class T, class R >
void successive_shortest_path_nonnegative_weights(Graph& g,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t,
    const bgl_named_params< P, T, R >& params)
{

    return detail::successive_shortest_path_nonnegative_weights_dispatch1(g, s,
        t,
        choose_const_pmap(get_param(params, edge_capacity), g, edge_capacity),
        choose_pmap(get_param(params, edge_residual_capacity), g,
            edge_residual_capacity),
        choose_const_pmap(get_param(params, edge_weight), g, edge_weight),
        choose_const_pmap(get_param(params, edge_reverse), g, edge_reverse),
        choose_const_pmap(get_param(params, vertex_index), g, vertex_index),
        get_param(params, vertex_predecessor), params);
}

template < class Graph >
void successive_shortest_path_nonnegative_weights(Graph& g,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t)
{
    bgl_named_params< int, buffer_param_t > params(0);
    successive_shortest_path_nonnegative_weights(g, s, t, params);
}

} // boost
#endif /* BOOST_GRAPH_SUCCESSIVE_SHORTEST_PATH_HPP */


#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <chrono>
#include <limits>
#include <float.h>

#include <CGAL/Polygon_mesh_processing/measure.h>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/trim.hpp>

#include "Utils.h"
#include "OBJ_printer.h"
#include "SymMat.h"
#include "cluster_settings.h"
#include "ErrorQuadric.h"
#include "Chart.h"
#include "JoinOperation.h"
#include "polyhedron_builder.h"
#include "eig.h"

#include "CGAL_typedefs.h"

#define SEPARATE_CHART_FILE false

Vector normalise(Vector v) {return v / std::sqrt(v.squared_length());}

//key: face_id, value: chart_id
std::map<uint32_t, uint32_t> chart_id_map;

void count_faces_in_active_charts(std::vector<Chart> &charts) {
  uint32_t active_faces = 0;
  for (auto& chart : charts)
  {
    if (chart.active)
    {
      active_faces += chart.facets.size();
    }
  }
  std::cout << "found " << active_faces << " active faces\n";
}

uint32_t
create_charts (Polyhedron &P, const double cost_threshold , const uint32_t chart_threshold, CLUSTER_SETTINGS cluster_settings){
  std::stringstream report;

  //calculate areas of each face
  std::cout << "Calculating face areas...\n";
  std::map<face_descriptor,double> fareas;
  for(face_descriptor fd: faces(P)){
    fareas[fd] = CGAL::Polygon_mesh_processing::face_area  (fd,P);
  }
  //calculate normals of each faces
  std::cout << "Calculating face normals...\n";
  std::map<face_descriptor,Vector> fnormals;
  CGAL::Polygon_mesh_processing::compute_face_normals(P,boost::make_assoc_property_map(fnormals));

  //get boost face iterator
  face_iterator fb_boost, fe_boost;
  boost::tie(fb_boost, fe_boost) = faces(P);

  //each face begins as its own chart
  std::cout << "Creating initial charts...\n";
  std::vector<Chart> charts;
  for ( Facet_iterator fb = P.facets_begin(); fb != P.facets_end(); ++fb){
    //assign id to face
    fb->id() = charts.size();

    // std::cout << "normal " << charts.size() << ": " << fnormals[*fb_boost] << std::endl;

    //init chart instance for face
    Chart c(charts.size(),*fb, fnormals[*fb_boost], fareas[*fb_boost]);
    charts.push_back(c);

    // //check uv coords saved
    // double u = fb->halfedge()->vertex()->point().get_u();
    // double v = fb->halfedge()->vertex()->point().get_v();
    // std::cout << "tex coords:  u " << u << ", v " << v << std::endl;

    fb_boost++;
  }

  //for reporting and calculating when to stop merging
  const uint32_t initial_charts = charts.size();
  const uint32_t desired_merges = initial_charts - chart_threshold;
  uint32_t chart_merges = 0;

  //create possible join list/queue. Each original edge in the mesh becomes a join (if not a boundary edge)
  std::cout << "Creating initial joins...\n";
  std::list<JoinOperation> joins;
  std::list<JoinOperation>::iterator it;

  int edgecount = 0;

  for( Edge_iterator eb = P.edges_begin(), ee = P.edges_end(); eb != ee; ++ eb){

    edgecount++;

    //only create join if halfedge is not a boundary edge
    if ( !(eb->is_border()) && !(eb->opposite()->is_border()) )
    {
          uint32_t face1 = eb->facet()->id();
          uint32_t face2 = eb->opposite()->facet()->id();
          JoinOperation join (face1,face2,JoinOperation::cost_of_join(charts[face1],charts[face2], cluster_settings));
          joins.push_back(join);
    }
  }

  std::cout << joins.size() << " joins\n" << edgecount << " edges\n";

  // join charts until target is reached
  int prev_cost_percent = -1;
  int prev_charts_percent = -1;
  int overall_percent = -1;

  joins.sort(JoinOperation::sort_joins);
  const double lowest_cost = joins.front().cost;

  //execute lowest join cost and update affected joins.  re-sort.
  std::cout << "Processing join queue...\n";
  while (joins.front().cost < cost_threshold
        &&  !joins.empty()
        &&  (charts.size() - chart_merges) > chart_threshold){

    //reporting-------------
    int percent = (int)(((joins.front().cost - lowest_cost) / (cost_threshold - lowest_cost)) * 100);
    if (percent != prev_cost_percent && percent > overall_percent) {
      prev_cost_percent = percent;
      overall_percent = percent;
      std::cout << percent << " percent complete\n";
    }
    percent = (int)(((float)chart_merges / (float)desired_merges) * 100);
    if (percent != prev_charts_percent && percent > overall_percent) {
      prev_charts_percent = percent;
      overall_percent = percent;
      std::cout << percent << " percent complete\n";
    }

    //implement the join with lowest cost
    JoinOperation join_todo = joins.front();
    joins.pop_front();

    // std::cout << "join cost : " << join_todo.cost << std::endl;

    //merge faces from chart2 into chart 1
    // std::cout << "merging charts " << join_todo.chart1_id << " and " << join_todo.chart2_id << std::endl;
    charts[join_todo.chart1_id].merge_with(charts[join_todo.chart2_id], join_todo.cost);

    //DEactivate chart 2
    if (charts[join_todo.chart2_id].active == false)
    {
      report << "chart " << join_todo.chart2_id << " was already inactive at merge " << chart_merges << std::endl; // should not happen
      continue;
    }
    charts[join_todo.chart2_id].active = false;

    int current_item = 0;
    std::list<int> to_erase;
    std::vector<JoinOperation> to_replace;

    //update itremaining joins that include either of the merged charts
    for (it = joins.begin(); it != joins.end(); ++it)
    {
      //if join is affected, update references and cost
      if (it->chart1_id == join_todo.chart1_id
         || it->chart1_id == join_todo.chart2_id
         || it->chart2_id == join_todo.chart1_id
         || it->chart2_id == join_todo.chart2_id )
      {

        //eliminate references to joined chart 2 (it is no longer active)
        // by pointing them to chart 1
        if (it->chart1_id == join_todo.chart2_id){
          it->chart1_id = join_todo.chart1_id;
        }
        if (it->chart2_id == join_todo.chart2_id){
          it->chart2_id = join_todo.chart1_id;
        }

        //search for duplicates
        if ((it->chart1_id == join_todo.chart1_id && it->chart2_id == join_todo.chart2_id)
          || (it->chart2_id == join_todo.chart1_id && it->chart1_id == join_todo.chart2_id) ){
          report << "duplicate found : c1 = " << it->chart1_id << ", c2 = " << it->chart2_id << std::endl;

          to_erase.push_back(current_item);
        }
        //check for joins within a chart
        else if (it->chart1_id == it->chart2_id)
        {
          report << "Join found within a chart: " << it->chart1_id << std::endl;
          to_erase.push_back(current_item);

        }
        else {
          //update cost with new cost
          it->cost = JoinOperation::cost_of_join(charts[it->chart1_id], charts[it->chart2_id], cluster_settings);

          //save this join to be deleted and replaced in correct position after deleting duplicates
          to_replace.push_back(*it);
          to_erase.push_back(current_item);
        }
      }
      current_item++;
    }

    //adjust ID to be deleted to account for previously deleted items
    to_erase.sort();
    int num_erased = 0;
    for (auto id : to_erase) {
      std::list<JoinOperation>::iterator it2 = joins.begin();
      std::advance(it2, id - num_erased);
      joins.erase(it2);
      num_erased++;
    }

    // replace joins that were filtered out to be sorted
    if (to_replace.size() > 0)
    {
      std::sort(to_replace.begin(), to_replace.end(), JoinOperation::sort_joins);
      std::list<JoinOperation>::iterator it2;
      uint32_t insert_item = 0;
      for (it2 = joins.begin(); it2 != joins.end(); ++it2){
        //insert items while join list item has bigger cost than element to be inserted
        while (it2->cost > to_replace[insert_item].cost
              && insert_item < to_replace.size()){
          joins.insert(it2, to_replace[insert_item]);
          insert_item++;
        }
        //if all items are in place, we are done
        if (insert_item >= to_replace.size())
        {
          break;
        }
      }
      //add any remaining items
      for (uint32_t i = insert_item; i < to_replace.size(); i++){
        joins.push_back(to_replace[i]);
      }
    }

#if 0
    //CHECK that each join would give a chart with at least 3 neighbours
    //TODO also need to check boundary edges
    to_erase.clear();
    std::vector<std::vector<uint32_t> > neighbour_count (charts.size(), std::vector<uint32_t>(0));
    std::list<JoinOperation>::iterator it2;
    for (it2 = joins.begin(); it2 != joins.end(); ++it2){
      //for chart 1 , add entry in vector for that chart containing id of chart 2
      // and vice versa
      neighbour_count[it2->chart1_id].push_back(it2->chart2_id);
      neighbour_count[it2->chart2_id].push_back(it2->chart1_id);
    }

    uint32_t join_id = 0;
    for (it2 = joins.begin(); it2 != joins.end(); ++it2){
      // combined neighbour count of joins' 2 charts should be at least 5
      // they will both contain each other (accounting for 2 neighbours) and require 3 more

      //merge the vectors for each chart in the join and count unique neighbours
      std::vector<uint32_t> combined_nbrs (neighbour_count[it2->chart1_id]);
      combined_nbrs.insert(combined_nbrs.end(), neighbour_count[it2->chart2_id].begin(), neighbour_count[it2->chart2_id].end());

      //find unique
      std::sort(combined_nbrs.begin(), combined_nbrs.end());
      uint32_t unique = 1;
      for (uint32_t i = 1; i < combined_nbrs.size(); i++){
        if (combined_nbrs[i] != combined_nbrs [i-1])
        {
          unique++;
        }
      }
      if (unique < 5)
      {
        to_erase.push_back(join_id);
      }
      join_id++;
    }
    //erase joins that would result in less than 3 corners
    to_erase.sort();
    num_erased = 0;
    for (auto id : to_erase) {
      std::list<JoinOperation>::iterator it2 = joins.begin();
      std::advance(it2, id - num_erased);
      joins.erase(it2);
      num_erased++;
    }
#endif

    chart_merges++;

  }

  // std::cout << "Printing Joins:\n";
  // int index = 0;
  // std::list<JoinOperation>::iterator it2;
  // for (it2 = joins.begin(); it2 != joins.end(); ++it2){
  //   std::cout << "Join " << ++index << ", cost " << it2->cost << std::endl;
  // }

  //reporting//testing

  std::cout << "front join cost: " << joins.front().cost << ", num joins: " << joins.size() << "chart threshold: " << chart_threshold << std::endl;

  std::cout << "--------------------\nCharts:\n----------------------\n";

  uint32_t total_faces = 0;
  uint32_t total_active_charts = 0;
  for (uint32_t i = 0; i < charts.size(); ++i)
  {
    if (charts[i].active)
    {
      uint32_t num_faces = charts[i].facets.size();
      total_faces += num_faces;
      total_active_charts++;
      // std::cout << "Chart " << i << " : " << num_faces << " faces" << std::endl;
    }
  }
  std::cout << "Total number of faces in charts = " << total_faces << std::endl;
  std::cout << "Initial charts = " << charts.size() << std::endl;
  std::cout << "Total number merges = " << chart_merges << std::endl;
  std::cout << "Total active charts = " << total_active_charts << std::endl;

  std::cout << "--------------------\nReport:\n----------------------\n";
  // std::cout << report.str();

  //populate LUT for face to chart mapping
  //count charts on the way to apply new chart ids
  uint32_t active_charts = 0;
  for (uint32_t id = 0; id < charts.size(); ++id) {
    auto& chart = charts[id];
    if (chart.active) {
      for (auto& f : chart.facets) {
        chart_id_map[f.id()] = active_charts;
      }
      active_charts++;
    }
  }

  return active_charts;

}

int main( int argc, char** argv )
{

  std::string obj_filename = "dino.obj";
  if (Utils::cmdOptionExists(argv, argv+argc, "-f")) {
    obj_filename = std::string(Utils::getCmdOption(argv, argv + argc, "-f"));
  }
  else {
    std::cout << "Please provide an obj filename using -f <filename.obj>" << std::endl;
    std::cout << "Optional: -ch specifies chart threshold (=100)" << std::endl;
    std::cout << "Optional: -co specifies cost threshold (=double max)" << std::endl;

    std::cout << "Optional: -ef specifies error fit coefficient (=1)" << std::endl;
    std::cout << "Optional: -eo specifies error orientation coefficient (=1)" << std::endl;
    std::cout << "Optional: -es specifies error shape coefficient (=1)" << std::endl;
    return 1;
  }

  double cost_threshold = std::numeric_limits<double>::max();
  if (Utils::cmdOptionExists(argv, argv+argc, "-co")) {
    cost_threshold = atof(Utils::getCmdOption(argv, argv + argc, "-co"));
  }
  uint32_t chart_threshold = 100;
  if (Utils::cmdOptionExists(argv, argv+argc, "-ch")) {
    chart_threshold = atoi(Utils::getCmdOption(argv, argv + argc, "-ch"));
  }
  double e_fit_cf = 1.0;
  if (Utils::cmdOptionExists(argv, argv+argc, "-ef")) {
    e_fit_cf = atof(Utils::getCmdOption(argv, argv + argc, "-ef"));
  }
  double e_ori_cf = 1.0;
  if (Utils::cmdOptionExists(argv, argv+argc, "-eo")) {
    e_ori_cf = atof(Utils::getCmdOption(argv, argv + argc, "-eo"));
  }
  double e_shape_cf = 1.0;
  if (Utils::cmdOptionExists(argv, argv+argc, "-es")) {
    e_shape_cf = atof(Utils::getCmdOption(argv, argv + argc, "-es"));
  }
  CLUSTER_SETTINGS cluster_settings (e_fit_cf, e_ori_cf, e_shape_cf);

    //load OBJ into arrays
  std::vector<double> vertices;
  std::vector<int> tris;
  std::vector<double> t_coords;
  std::vector<int> tindices;
  Utils::load_obj( obj_filename, vertices, tris, t_coords, tindices);

  if (vertices.size() == 0 ) {
    std::cout << "didnt find any vertices" << std::endl;
    return 1;
  }
  std::cout << "Mesh loaded (" << vertices.size() / 3 << " vertices, " << tris.size() / 3 << " faces, " << t_coords.size() / 2 << " tex coords)" << std::endl;

  auto start_time = std::chrono::system_clock::now();

  // build a polyhedron from the loaded arrays
  Polyhedron polyMesh;
  bool check_vertices = true;
  polyhedron_builder<HalfedgeDS> builder( vertices, tris, t_coords, tindices, check_vertices );

  polyMesh.delegate( builder );

  if (polyMesh.is_valid(false)){
    std::cout << "mesh valid\n";
  }

  if (!CGAL::is_triangle_mesh(polyMesh)){
    std::cerr << "Input geometry is not triangulated." << std::endl;
    return EXIT_FAILURE;
  }
  else {
    std::cout << "mesh is triangulated\n";
  }

  uint32_t active_charts = create_charts(polyMesh, cost_threshold, chart_threshold, cluster_settings);

  std::string out_filename = "data/charts.obj";
  std::ofstream ofs( out_filename );
  OBJ_printer::print_polyhedron_wavefront_with_charts( ofs, polyMesh,chart_id_map, active_charts, SEPARATE_CHART_FILE);
  ofs.close();
  std::cout << "simplified mesh was written to " << out_filename << std::endl;

  //Logging
  auto time = std::chrono::system_clock::now();
  std::chrono::duration<double> diff = time - start_time;
  std::time_t now_c = std::chrono::system_clock::to_time_t(time);
  std::string log_path = "../../data/logs/chart_creation_log.txt";
  ofs.open (log_path, std::ofstream::out | std::ofstream::app);
  ofs << "\n-------------------------------------\n";

  ofs << "Executed at " << std::put_time(std::localtime(&now_c), "%F %T") << std::endl;
  ofs << "Ran for " << (int)diff.count() / 60 << " m "<< (int)diff.count() % 60 << " s" << std::endl;
  ofs << "Input file: " << obj_filename << "\nOutput file: " << out_filename << std::endl;
  ofs << "Vertices: " << vertices.size()/3 << " , faces: " << tris.size()/3 << std::endl;
  ofs << "Desired Charts: " << chart_threshold << ", active charts: " << active_charts << std::endl;
  ofs << "Cost threshold: " << cost_threshold << std::endl;
  ofs << "Cluster settings: e_fit: " << cluster_settings.e_fit_cf << ", e_ori: " << cluster_settings.e_ori_cf << ", e_shape" << cluster_settings.e_shape_cf << std::endl;

    ofs.close();
  std::cout << "Log written to " << log_path << std::endl;

  return EXIT_SUCCESS ;
}


#include <boost/test/unit_test.hpp>
#include <templ/solvers/temporal/LoosePathConsistency.hpp>
#include <graph_analysis/GraphIO.hpp>

using namespace templ::solvers;
using namespace templ::solvers::temporal;
using namespace graph_analysis;

BOOST_AUTO_TEST_SUITE(loose_path_consistency)

BOOST_AUTO_TEST_CASE(operation_intersection)
{
    Bounds::List a;
    a.push_back(Bounds(1,4));
    a.push_back(Bounds(10,15));

    Bounds::List b;
    b.push_back(Bounds(3,11));
    b.push_back(Bounds(14,19));

    {
        Bounds::List intersection = LoosePathConsistency::intersection(a, b);

        Bounds::List expectedIntersection;
        expectedIntersection.push_back(Bounds(3,4));
        expectedIntersection.push_back(Bounds(10,11));
        expectedIntersection.push_back(Bounds(14,15));

        BOOST_REQUIRE_MESSAGE(expectedIntersection == intersection, "Expected intersection: " << Bounds::toString(expectedIntersection) << " got " << Bounds::toString(intersection));

    }

    {
        Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a, b);
        BOOST_REQUIRE_MESSAGE(looseIntersection.size() == 2, "Bounds loose looseIntersection should be of size 2, but was " << looseIntersection.size());

        Bounds::List expectedIntersection;
        expectedIntersection.push_back(Bounds(3,4));
        expectedIntersection.push_back(Bounds(10,15));

        BOOST_REQUIRE_MESSAGE(expectedIntersection == looseIntersection, "Expected looseIntersection: " << Bounds::toString(expectedIntersection) << " got " << Bounds::toString(looseIntersection));
    }
}

BOOST_AUTO_TEST_CASE(operation_composition)
{
    {
        Bounds::List a;
        a.push_back(Bounds(-1.25,0.25));
        a.push_back(Bounds(2.75, 4.25));

        Bounds::List b;
        b.push_back(Bounds(-0.25,1.25));
        b.push_back(Bounds(3.75,4.25));

        {
            Bounds::List composition = LoosePathConsistency::composition(a, b);
            Bounds::List expectedComposition;
            expectedComposition.push_back(Bounds(-1.50,1.50));
            expectedComposition.push_back(Bounds(2.50,5.50));
            expectedComposition.push_back(Bounds(6.50,8.50));

            BOOST_REQUIRE_MESSAGE(expectedComposition == composition, "Expected composition: " << Bounds::toString(expectedComposition) << " got " << Bounds::toString(composition));
        }
    }

    {
        Bounds::List a;
        a.push_back(Bounds(-100, -80));

        Bounds::List b;
        b.push_back(Bounds(10, 120));

        Bounds::List composition = LoosePathConsistency::composition(a,b);
        Bounds::List expectedComposition;
        expectedComposition.push_back(Bounds(-90,40));
        BOOST_REQUIRE_MESSAGE(expectedComposition == composition, "Expected composition: " << Bounds::toString(expectedComposition) << " got " << Bounds::toString(composition));
    }

    {
        Bounds::List a;
        a.push_back(Bounds(80, 100));
        a.push_back(Bounds(150, 160));
        a.push_back(Bounds(180, 190));

        Bounds::List b;
        b.push_back(Bounds(-40, -30));
        // -150 is wrong, but must be -140 to come to the require results
        b.push_back(Bounds(-140, -130));

        Bounds::List composition = LoosePathConsistency::composition(a,b);
        Bounds::List expectedComposition;
        expectedComposition.push_back(Bounds(-60,-30));
        expectedComposition.push_back(Bounds(10,30));
        expectedComposition.push_back(Bounds(40,70));
        expectedComposition.push_back(Bounds(110,130));
        expectedComposition.push_back(Bounds(140,160));

        BOOST_REQUIRE_MESSAGE(expectedComposition == composition, "Expected composition: " << Bounds::toString(expectedComposition) << " got " << Bounds::toString(composition));
    }

    {
        Bounds::List a;
        a.push_back(Bounds(20, 40));
        a.push_back(Bounds(100, 130));

        Bounds::List b;
        b.push_back(Bounds(50,70));
        b.push_back(Bounds(110,120));
        b.push_back(Bounds(130,140));
        b.push_back(Bounds(160,190));

        Bounds::List composition = LoosePathConsistency::composition(a,b);
        Bounds::List expectedComposition;
        // Commenting out which will be merged
        // a[0] intersect with b
        expectedComposition.push_back(Bounds(70,110));
        //expectedComposition.push_back(Bounds(130,160));
        //expectedComposition.push_back(Bounds(150,180));
        //expectedComposition.push_back(Bounds(180,230));

        // a[1] intersect with b
        //expectedComposition.push_back(Bounds(150,200));
        //expectedComposition.push_back(Bounds(210,250));
        //expectedComposition.push_back(Bounds(230,270));
        //expectedComposition.push_back(Bounds(260,320));

        // Intermediate intersections by merging overlapping
        //expectedComposition.push_back(Bounds(130,200));
        //expectedComposition.push_back(Bounds(210,320));
        //
        // Final intersection
        expectedComposition.push_back(Bounds(130,320));

        BOOST_REQUIRE_MESSAGE(expectedComposition == composition, "Expected composition: " << Bounds::toString(expectedComposition) << " got " << Bounds::toString(composition));
    }
}

BOOST_AUTO_TEST_CASE(operation_compute_disjoint_intervals)
{
    Bounds::List a;
    a.push_back(Bounds(0,10));
    a.push_back(Bounds(9,10));
    a.push_back(Bounds(9,20));
    a.push_back(Bounds(23,30));

    Bounds::List expected;
    expected.push_back(Bounds(0,20));
    expected.push_back(Bounds(23,30));

    Bounds::List result = LoosePathConsistency::computeDisjointIntervals(a);
    BOOST_REQUIRE_MESSAGE(expected == result, "Expected interval list: " << Bounds::toString(expected)
            << " got " << Bounds::toString(result) << "(size: " << result.size() << ")");
}

BOOST_AUTO_TEST_CASE(operation_loose_path)
{
    {
        Bounds::List a0_1;
        a0_1.push_back(Bounds(10,20));
        a0_1.push_back(Bounds(100,110));

        Bounds::List a1_2;
        a1_2.push_back(Bounds(20,40));
        a1_2.push_back(Bounds(100,110));

        // Check triangle a0_1 with completing vertex 2
        // empty constraint between 0 and 2, so composition must be empty
        Bounds::List composition = LoosePathConsistency::composition(a0_1, Bounds::List());
        Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a0_1, composition);

        Bounds::List expectedLooseIntersection;

        BOOST_REQUIRE_MESSAGE(looseIntersection == expectedLooseIntersection, "Expected loose intersection: " << Bounds::toString(expectedLooseIntersection) << " got " << Bounds::toString(looseIntersection) );

        Bounds::List a1_0 = Bounds::reverse(a0_1);
        Bounds::List a2_1 = Bounds::reverse(a1_2);

        {
            Bounds::List composition = LoosePathConsistency::composition(a1_0, Bounds::List());
            Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a1_0, composition);

            BOOST_REQUIRE_MESSAGE(looseIntersection == expectedLooseIntersection, "Expected loose intersection: " << Bounds::toString(expectedLooseIntersection) << " got " << Bounds::toString(looseIntersection) );
        }
    }
    {
        Bounds::List a0_1;
        a0_1.push_back(Bounds(10,20));
        a0_1.push_back(Bounds(100,110));
        Bounds::List a1_0 = Bounds::reverse(a0_1);

        Bounds::List a1_3;
        a1_3.push_back(Bounds(30,40));
        a1_3.push_back(Bounds(130,150));
        Bounds::List a3_1 = Bounds::reverse(a1_3);

        //a3_1.push_back(Bounds(-40, -30));
        //a3_1.push_back(Bounds(-150, -130));

        Bounds::List a0_3;
        a0_3.push_back(Bounds(80,100));
        a0_3.push_back(Bounds(150,160));
        a0_3.push_back(Bounds(180,190));
        Bounds::List a3_0 = Bounds::reverse(a0_3);

        {
            // Check triangle a0_1 with completing vertex 3
            Bounds::List composition = LoosePathConsistency::composition(a0_3, a3_1);
            BOOST_TEST_MESSAGE("Composition: " << Bounds::toString(composition) );

            Bounds::List intersection = LoosePathConsistency::intersection(a0_1, composition);
            BOOST_TEST_MESSAGE("Intersection: " << Bounds::toString(intersection) );

            Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a0_1, composition);

            Bounds::List expectedLooseIntersection;
            expectedLooseIntersection.push_back(Bounds(10,20));
            expectedLooseIntersection.push_back(Bounds(110,110));

            BOOST_REQUIRE_MESSAGE(looseIntersection == expectedLooseIntersection, "Expected loose intersection: " << Bounds::toString(expectedLooseIntersection) << " got " << Bounds::toString(looseIntersection) );
        }
        // check reverse
        {
            Bounds::List composition = LoosePathConsistency::composition(a1_3, a3_0);
            BOOST_TEST_MESSAGE("Composition: " << Bounds::toString(composition) );

            Bounds::List intersection = LoosePathConsistency::intersection(a1_0, composition);
            BOOST_TEST_MESSAGE("Intersection: " << Bounds::toString(intersection) );

            Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a1_0, composition);

            Bounds::List expectedLooseIntersection;
            expectedLooseIntersection.push_back(Bounds(-20,-10));
            expectedLooseIntersection.push_back(Bounds(-110,-110));

            BOOST_REQUIRE_MESSAGE(looseIntersection == expectedLooseIntersection, "Reverse check: expected loose intersection: " << Bounds::toString(expectedLooseIntersection) << " got " << Bounds::toString(looseIntersection) );
        }

        {
            Bounds::List composition = LoosePathConsistency::composition(a0_1, a1_3);
            BOOST_TEST_MESSAGE("Composition: " << Bounds::toString(composition) );
            Bounds::List looseIntersection = LoosePathConsistency::looseIntersection(a0_3, composition);

            Bounds::List expectedLooseIntersection;
            expectedLooseIntersection.push_back(Bounds(150,160));

            BOOST_REQUIRE_MESSAGE(looseIntersection == expectedLooseIntersection, "Expected loose intersection: " << Bounds::toString(expectedLooseIntersection) << " got " << Bounds::toString(looseIntersection) );
        }

    }

}

/**
 *       x0   -- [10,20] [100,110]   --> X1
 *       |                          /
 *       |                        /
 *  [180,190]              [130,150]
 *       |
 *       |      /
 *            /
 *           /
 *         |_
 *       x3
 *
 * \see http://www.ics.uci.edu/~csp/R40.pdf p.18
 */
BOOST_AUTO_TEST_CASE(example_network_0)
{
    TemporalConstraintNetwork tcn,expected;
    point_algebra::TimePoint::Ptr v0(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v0->setLabel("v0");
    point_algebra::TimePoint::Ptr v1(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v1->setLabel("v1");
    point_algebra::TimePoint::Ptr v2(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v2->setLabel("v2");
    point_algebra::TimePoint::Ptr v3(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v3->setLabel("v3");

    // Designing start
    {
        IntervalConstraint::Ptr i0(new IntervalConstraint(v0,v1));
        i0->addInterval(Bounds(10,30));

        IntervalConstraint::Ptr i1(new IntervalConstraint(v1,v2));
        i1->addInterval(Bounds(10,30));

        IntervalConstraint::Ptr i2(new IntervalConstraint(v2,v3));
        i2->addInterval(Bounds(100,120));

        IntervalConstraint::Ptr i3(new IntervalConstraint(v1,v3));
        i3->addInterval(Bounds(130,150));

        IntervalConstraint::Ptr i4(new IntervalConstraint(v0,v3));
        i4->addInterval(Bounds(140,160));
        i4->addInterval(Bounds(240,250));

        IntervalConstraint::Ptr i5(new IntervalConstraint(v0,v2));
        i5->addInterval(Bounds(30,50));

        tcn.addIntervalConstraint(i0);
        tcn.addIntervalConstraint(i1);
        tcn.addIntervalConstraint(i2);
        tcn.addIntervalConstraint(i3);
        tcn.addIntervalConstraint(i4);
        tcn.addIntervalConstraint(i5);
    }

    // Designing expected
    {
        IntervalConstraint::Ptr e0(new IntervalConstraint(v0,v1));
        e0->addInterval(Bounds(10,30));
        IntervalConstraint::Ptr e1(new IntervalConstraint(v1,v2));
        e1->addInterval(Bounds(10,30));
        IntervalConstraint::Ptr e2(new IntervalConstraint(v2,v3));
        e2->addInterval(Bounds(100,120));
        IntervalConstraint::Ptr e3(new IntervalConstraint(v1,v3));
        e3->addInterval(Bounds(130,150));
        IntervalConstraint::Ptr e4(new IntervalConstraint(v0,v3));
        e4->addInterval(Bounds(140,160));
        IntervalConstraint::Ptr e5(new IntervalConstraint(v0,v2));
        e5->addInterval(Bounds(30,50));

        expected.addIntervalConstraint(e0);
        expected.addIntervalConstraint(e1);
        expected.addIntervalConstraint(e2);
        expected.addIntervalConstraint(e3);
        expected.addIntervalConstraint(e4);
        expected.addIntervalConstraint(e5);
    }

    TemporalConstraintNetwork intersectionNetwork = LoosePathConsistency::intersectionNetwork(tcn);
    TemporalConstraintNetwork result = LoosePathConsistency::looseIntersectionNetwork(tcn, intersectionNetwork);

    BaseGraph::Ptr graph = result.getDistanceGraph();
    BOOST_TEST_MESSAGE("Write result graph: " << graph->size() << " order: " << graph->order());
    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-example_0-loose_intersection_network-result.gexf", graph);
    BOOST_REQUIRE_MESSAGE(expected.equals(graph), "Expected equal graphs, actual: "<<expected.equals(graph));
}

/**
 *       x0   -- [10,20] [100,110]   --> X1
 *       |                          /   |
 *       |                        /
 *  [80,100]          [30,40]   /
 *  [150,160]       [130,150*!] /          [20,40]
 *  [180,190]                            [100,130]
 *       |        /
 *       |      /
 *            /
 *           /
 *         |_
 *       x3 <--                      -- x2
 *             [50,70] [110,120]
 *             [130,140] [160,190]
 *
 * \see http://www.ics.uci.edu/~csp/R40.pdf p.18
 *
 * *!There is actually an error in the example the paper provides, it has to be
 * [30,40][130,140] for the intervall between x3->x1
 */
BOOST_AUTO_TEST_CASE(example_network_1)
{
    TemporalConstraintNetwork tcn,expected;
    point_algebra::TimePoint::Ptr v0(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v0->setLabel("v0");
    point_algebra::TimePoint::Ptr v1(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v1->setLabel("v1");
    point_algebra::TimePoint::Ptr v2(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v2->setLabel("v2");
    point_algebra::TimePoint::Ptr v3(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v3->setLabel("v3");

    // Designing start
    {
        IntervalConstraint::Ptr i0(new IntervalConstraint(v0,v1));
        i0->addInterval(Bounds(10,20));
        i0->addInterval(Bounds(100,110));

        IntervalConstraint::Ptr i1(new IntervalConstraint(v1,v2));
        i1->addInterval(Bounds(20,40));
        i1->addInterval(Bounds(100, 130));

        IntervalConstraint::Ptr i2(new IntervalConstraint(v2,v3));
        i2->addInterval(Bounds(50, 70));
        i2->addInterval(Bounds(110, 120));
        i2->addInterval(Bounds(130, 140));
        i2->addInterval(Bounds(160, 190));

        IntervalConstraint::Ptr i3(new IntervalConstraint(v1,v3));
        i3->addInterval(Bounds(30,40));
        i3->addInterval(Bounds(130,140));

        IntervalConstraint::Ptr i4(new IntervalConstraint(v0,v3));
        i4->addInterval(Bounds(80,100));
        i4->addInterval(Bounds(150,160));
        i4->addInterval(Bounds(180,190));

        tcn.addIntervalConstraint(i0);
        tcn.addIntervalConstraint(i1);
        tcn.addIntervalConstraint(i2);
        tcn.addIntervalConstraint(i3);
        tcn.addIntervalConstraint(i4);
    }

    // Designing expected
    {
        IntervalConstraint::Ptr e0(new IntervalConstraint(v0,v1));
        e0->addInterval(Bounds(10,20));
        IntervalConstraint::Ptr e1(new IntervalConstraint(v1,v2));
        e1->addInterval(Bounds(20,30));
        IntervalConstraint::Ptr e2(new IntervalConstraint(v2,v3));
        e2->addInterval(Bounds(110,120));
        IntervalConstraint::Ptr e3(new IntervalConstraint(v1,v3));
        e3->addInterval(Bounds(130,140));
        IntervalConstraint::Ptr e4(new IntervalConstraint(v0,v3));
        e4->addInterval(Bounds(150,160));
        IntervalConstraint::Ptr e5(new IntervalConstraint(v0,v2));
        e5->addInterval(Bounds(30,50));

        expected.addIntervalConstraint(e0);
        expected.addIntervalConstraint(e1);
        expected.addIntervalConstraint(e2);
        expected.addIntervalConstraint(e3);
        expected.addIntervalConstraint(e4);
        expected.addIntervalConstraint(e5);
    }

    BOOST_REQUIRE_MESSAGE(tcn.getEdgeNumber() == 5,"Expected: 5 edges, Actual: "<<tcn.getEdgeNumber());

    TemporalConstraintNetwork result = LoosePathConsistency::loosePathConsistency(tcn);

    BaseGraph::Ptr graph = result.getDistanceGraph();
    {
        BOOST_TEST_MESSAGE("Write result graph: " << graph->size() << " order: " << graph->order());
        EdgeIterator::Ptr e = graph->getEdgeIterator();
        while(e->next())
        {
            Edge::Ptr edge = e->current();
            BOOST_TEST_MESSAGE(edge->toString() << " " << edge->getLabel());
        }
        VertexIterator::Ptr v = graph->getVertexIterator();
        while(v->next())
        {
            Vertex::Ptr vertex = v->current();
            BOOST_TEST_MESSAGE(vertex->toString() << " " << vertex->getLabel());
        }
    }
    {
        BOOST_TEST_MESSAGE("Write expected graph: " << expected.getDistanceGraph()->size() << " order: " << expected.getDistanceGraph()->order());
        EdgeIterator::Ptr e = expected.getDistanceGraph()->getEdgeIterator();
        while(e->next())
        {
            Edge::Ptr edge = e->current();
            BOOST_TEST_MESSAGE(edge->toString() << " " << edge->getLabel());
        }
        VertexIterator::Ptr v = expected.getDistanceGraph()->getVertexIterator();
        while(v->next())
        {
            Vertex::Ptr vertex = v->current();
            BOOST_TEST_MESSAGE(vertex->toString() << " " << vertex->getLabel());
        }
    }

    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-example_1-network-result.gexf", graph);
    BOOST_REQUIRE_MESSAGE(expected.equals(graph), "Expected equal graphs, actual: "<<expected.equals(graph));
}

BOOST_AUTO_TEST_CASE(intersection_network)
{
    TemporalConstraintNetwork tcn,expected;
    point_algebra::TimePoint::Ptr v0(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v0->setLabel("v0");
    point_algebra::TimePoint::Ptr v1(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v1->setLabel("v1");
    point_algebra::TimePoint::Ptr v2(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v2->setLabel("v2");
    point_algebra::TimePoint::Ptr v3(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v3->setLabel("v3");

    IntervalConstraint::Ptr i0(new IntervalConstraint(v0,v1));
    i0->addInterval(Bounds(10,20));
    i0->addInterval(Bounds(110,110));
    IntervalConstraint::Ptr i1(new IntervalConstraint(v1,v2));
    i1->addInterval(Bounds(20,30));
    IntervalConstraint::Ptr i2(new IntervalConstraint(v2,v3));
    i2->addInterval(Bounds(110,120));
    IntervalConstraint::Ptr i3(new IntervalConstraint(v1,v3));
    i3->addInterval(Bounds(130,140));
    IntervalConstraint::Ptr i4(new IntervalConstraint(v0,v3));
    i4->addInterval(Bounds(150,160));
    IntervalConstraint::Ptr i5(new IntervalConstraint(v0,v2));
    i5->addInterval(Bounds(30,140));

    tcn.addIntervalConstraint(i0);
    tcn.addIntervalConstraint(i1);
    tcn.addIntervalConstraint(i2);
    tcn.addIntervalConstraint(i3);
    tcn.addIntervalConstraint(i4);
    tcn.addIntervalConstraint(i5);

    IntervalConstraint::Ptr e0(new IntervalConstraint(v0,v1));
    e0->addInterval(Bounds(10,30));
    IntervalConstraint::Ptr e1(new IntervalConstraint(v1,v2));
    e1->addInterval(Bounds(10,30));
    IntervalConstraint::Ptr e2(new IntervalConstraint(v2,v3));
    e2->addInterval(Bounds(100,120));
    IntervalConstraint::Ptr e3(new IntervalConstraint(v1,v3));
    e3->addInterval(Bounds(130,150));
    IntervalConstraint::Ptr e4(new IntervalConstraint(v0,v3));
    e4->addInterval(Bounds(140,160));
    e4->addInterval(Bounds(240,250));
    IntervalConstraint::Ptr e5(new IntervalConstraint(v0,v2));
    e5->addInterval(Bounds(30,50));

    expected.addIntervalConstraint(e0);
    expected.addIntervalConstraint(e1);
    expected.addIntervalConstraint(e2);
    expected.addIntervalConstraint(e3);
    expected.addIntervalConstraint(e4);
    expected.addIntervalConstraint(e5);

    BOOST_REQUIRE_MESSAGE(tcn.getEdgeNumber() == 6,"Expected: 6, Actual: "<<tcn.getEdgeNumber());

    TemporalConstraintNetwork result = LoosePathConsistency::intersectionNetwork(tcn);
    BaseGraph::Ptr graph = result.getDistanceGraph();

    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-intersection_network-result.gexf", graph);
    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-intersection_network-result.dot", graph);

    BOOST_REQUIRE_MESSAGE(expected.equals(graph), "Expected: 1, Actual: " << expected.equals(graph));
}

BOOST_AUTO_TEST_CASE(loose_intersection)
{
    TemporalConstraintNetwork tcn,tcn2,expected;

    point_algebra::TimePoint::Ptr v0(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v0->setLabel("v0");
    point_algebra::TimePoint::Ptr v1(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v1->setLabel("v1");
    point_algebra::TimePoint::Ptr v2(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v2->setLabel("v2");
    point_algebra::TimePoint::Ptr v3(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    v3->setLabel("v3");

    IntervalConstraint::Ptr i0(new IntervalConstraint(v0,v1));
    i0->addInterval(Bounds(10,30));
    IntervalConstraint::Ptr i1(new IntervalConstraint(v1,v2));
    i1->addInterval(Bounds(10,30));
    IntervalConstraint::Ptr i2(new IntervalConstraint(v2,v3));
    i2->addInterval(Bounds(100,120));
    IntervalConstraint::Ptr i3(new IntervalConstraint(v1,v3));
    i3->addInterval(Bounds(130,150));
    IntervalConstraint::Ptr i4(new IntervalConstraint(v0,v3));
    i4->addInterval(Bounds(140,160));
    i4->addInterval(Bounds(240,250));
    IntervalConstraint::Ptr i5(new IntervalConstraint(v0,v2));
    i5->addInterval(Bounds(30,50));

    tcn.addIntervalConstraint(i0);
    tcn.addIntervalConstraint(i1);
    tcn.addIntervalConstraint(i2);
    tcn.addIntervalConstraint(i3);
    tcn.addIntervalConstraint(i4);
    tcn.addIntervalConstraint(i5);

    IntervalConstraint::Ptr p0(new IntervalConstraint(v0,v1));
    p0->addInterval(Bounds(10,20));
    p0->addInterval(Bounds(110,110));
    IntervalConstraint::Ptr p1(new IntervalConstraint(v1,v2));
    p1->addInterval(Bounds(20,30));
    IntervalConstraint::Ptr p2(new IntervalConstraint(v2,v3));
    p2->addInterval(Bounds(110,120));
    IntervalConstraint::Ptr p3(new IntervalConstraint(v1,v3));
    p3->addInterval(Bounds(130,140));
    IntervalConstraint::Ptr p4(new IntervalConstraint(v0,v3));
    p4->addInterval(Bounds(150,160));
    IntervalConstraint::Ptr p5(new IntervalConstraint(v0,v2));
    p5->addInterval(Bounds(30,140));

    tcn2.addIntervalConstraint(p0);
    tcn2.addIntervalConstraint(p1);
    tcn2.addIntervalConstraint(p2);
    tcn2.addIntervalConstraint(p3);
    tcn2.addIntervalConstraint(p4);
    tcn2.addIntervalConstraint(p5);

    IntervalConstraint::Ptr e0(new IntervalConstraint(v0,v1));
    e0->addInterval(Bounds(10,20));
    IntervalConstraint::Ptr e1(new IntervalConstraint(v1,v2));
    e1->addInterval(Bounds(20,30));
    IntervalConstraint::Ptr e2(new IntervalConstraint(v2,v3));
    e2->addInterval(Bounds(110,120));
    IntervalConstraint::Ptr e3(new IntervalConstraint(v1,v3));
    e3->addInterval(Bounds(130,140));
    IntervalConstraint::Ptr e4(new IntervalConstraint(v0,v3));
    e4->addInterval(Bounds(150,160));
    IntervalConstraint::Ptr e5(new IntervalConstraint(v0,v2));
    e5->addInterval(Bounds(30,50));

    expected.addIntervalConstraint(e0);
    expected.addIntervalConstraint(e1);
    expected.addIntervalConstraint(e2);
    expected.addIntervalConstraint(e3);
    expected.addIntervalConstraint(e4);
    expected.addIntervalConstraint(e5);

    BOOST_REQUIRE_MESSAGE(tcn.getEdgeNumber() == 6,"Expected: 6, Actual: "<<tcn.getEdgeNumber());

    TemporalConstraintNetwork result = LoosePathConsistency::looseIntersectionNetwork(tcn,tcn2);
    BaseGraph::Ptr graph = result.getDistanceGraph();

    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-loose_network-result.gexf", graph);
    BOOST_REQUIRE_MESSAGE(expected.equals(graph), "Expected: 1, Actual: "<<expected.equals(graph));

}

/*
INPUT:
    v1 --[0,1] [16,17] [23,24]----> v2
    ^                           ^
    .                   [34,50]
    .                [23,33]
  [1,2]           [0,22]
  [11,12]        .
  [21,22]     .
    .      .
    .   .
    v0

EXPECTED (after execution of loose path-consistency):
    v1 --[0,1] [16,17] [23,24]----> v2
    ^                           ^
    .                   [34,46]
    .                [23,29]
  [1,2]           [1,22]
  [11,12]        .
  [21,22]     .
    .      .
    .   .
    v0

*/
BOOST_AUTO_TEST_CASE(lpc)
{
    TemporalConstraintNetwork tcn,expected;

    point_algebra::TimePoint::Ptr v0(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    point_algebra::TimePoint::Ptr v1(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));
    point_algebra::TimePoint::Ptr v2(new point_algebra::TimePoint(0,std::numeric_limits<uint64_t>::infinity()));

    IntervalConstraint::Ptr i0(new IntervalConstraint(v0,v1));
    i0->addInterval(Bounds(1,2));
    i0->addInterval(Bounds(11,12));
    i0->addInterval(Bounds(21,22));
    IntervalConstraint::Ptr i1(new IntervalConstraint(v1,v2));
    i1->addInterval(Bounds(0,1));
    i1->addInterval(Bounds(16,17));
    i1->addInterval(Bounds(23,24));
    IntervalConstraint::Ptr i2(new IntervalConstraint(v0,v2));
    i2->addInterval(Bounds(0,22));
    i2->addInterval(Bounds(23,33));
    i2->addInterval(Bounds(34,50));
    IntervalConstraint::Ptr i3(new IntervalConstraint(v0,v2));
    i3->addInterval(Bounds(1,22));
    i3->addInterval(Bounds(23,29));
    i3->addInterval(Bounds(34,46));

    tcn.addIntervalConstraint(i0);
    tcn.addIntervalConstraint(i1);
    tcn.addIntervalConstraint(i2);

    expected.addIntervalConstraint(i0);
    expected.addIntervalConstraint(i1);
    expected.addIntervalConstraint(i3);

    TemporalConstraintNetwork result = LoosePathConsistency::loosePathConsistency(tcn);
    BaseGraph::Ptr graph = result.getDistanceGraph();

    graph_analysis::io::GraphIO::write("/tmp/templ-test-loose_path_consistency-lpc-result.gexf", graph);
    BOOST_REQUIRE_MESSAGE(expected.equals(graph),"Expected: 1, Actual: "<<expected.equals(graph));
}

BOOST_AUTO_TEST_SUITE_END()


/*
** Copyright (C) 2011 Centre for Bioinformatics, Biomarker Discovery and Information-Based Medicine
** http://www.newcastle.edu.au/research-centre/cibm/
**
** Code for Paper:
** FS-kNN: A Fast and Scalable kNN Computation Technique using GPU,
** Ahmed Shamsul Arefin, Carlos Riveros, Regina Berrettaand Pablo Moscato,
** Email: ASA∗ - ahmed.arefin@newcastle.edu.au; CR - Carlos.Riveros@newcastle.edu.au ; RB - regina.berretta.@newcastle.edu.au ; PM
** pablo.moscato@newcastle.edu.au
**
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
** main.cpp // requires CUDA API, Boost library to compile
**
**
*/

/* Requires boost library from boost.org*/

#include <fstream>
#include <string>
#include <boost/foreach.hpp>
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
#include <sstream>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <time.h>

#include "computeDistance.h"

using std::vector;
using namespace std;

/*
 * This function has been implemented using boost tokenizer.
 * Purpose: Load a Microarray data in CIBM (http://cibm.newcastle.edu.au/ format and
 *
 * Input: microarray_cibm_format.txt
 *
 * <MicroarrayData>
 * 10	6
 * F_1	1	4.5	2	 7.3  8.21	5.71
 * F_2	1	3	8	 6.66 7.19	1.06
 * F_3	3	3	9	 5	  6.67	7.73
 * F_4	4	4	1	 4	  3.64	2.92
 * F_5	8	7	1.5	 4	  4.91	6.15
 * F_6	8	6	3	 2.4  7.36	4.76
 * F_7	3	8	3	 3.2  5.31	5.49
 * F_8	2	1	4.02 5.8  3.08	7.15
 * F_9	4	1	3.7	 2.2  1.31	1.48
 * F_10	-3	0	3	 1	  3.82	2.7
 * <SamplesNames>
 * 	C1	C2	C3	C4	C5	C6
 * <SamplesClasses>
 * 	1	1	1	0	0	0
 * <EndOfFile>
 *
 *
 * Output: none, store the matrix in a single dimensional global array input[]
 * the value of k will be set as k= int(log(n))+1
 *
 */

void readMicroarray(std::ifstream& filename){

		int i,j;

		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
		getline(filename,line);
		boost::char_separator<char> sep("	,. \n");
		tokenizer tok(line, sep);
		tokenizer::iterator  it=tok.begin();
		token = *it;
		nRow=atoi(token.c_str());

		/* Initialise k=log(n)+1 */

		k = (int)log((double)nRow)+1;

		/* Initialise chunkSize */

		/*if (nRow > 0  && nRow <=25000)
			chunkSize =4096;
		else if (nRow > 25000  && nRow <=100000)
			chunkSize = 16384;
		else if (nRow > 100000  && nRow <=2000000)*/
			//chunkSize = 65536;
			chunkSize = 16384;
		/*else
		cout<< "Data can not be loaded into GPU's in-memory, please use the external memory version"<<endl;
*/

		it++;
		token = *it;
		nCol=atoi(token.c_str());

		cout <<"Features (nRow) = "<<nRow << "  Samples(nCol) = "<<nCol<<endl;

		Ina = (float*)malloc(sizeof(float)*nRow*nCol);

		std::ofstream out (filenameNode.c_str());
		out<<nRow<<endl;

		for (i=0;i<nRow;i++){

			getline(filename,line);
			boost::char_separator<char> sep("	");
			tokenizer tok(line, sep);
			tokenizer::iterator  it=tok.begin();
			token = *it;
			nameGenes.push_back(token);
			out <<nameGenes[i]<<endl;
			it++;

			for (j=0;j<nCol;j++){
				token = *it;
				*(Ina+(nCol*i+j)) =  atof(token.c_str());
				it++;
				}
			}

		cout<<"Data file loaded successfully...!";

		{
			getline(filename,line);
			boost::char_separator<char> sep("	,. \n");
			tokenizer tok(line, sep);
			tokenizer::iterator  it=tok.begin();
			token = *it;

			{
				getline(filename,line);
				boost::char_separator<char> sep("	");
				tokenizer tok(line, sep);
				tokenizer::iterator  it=tok.begin();

				for (i=0;i<nCol;i++){
					token = *it;
					nameExp.push_back(token);
					it++;
				}
			}
		}

		{
			getline(filename,line);
			boost::char_separator<char> sep("	,. \n");
			tokenizer tok(line, sep);
			tokenizer::iterator  it=tok.begin();
			token = *it;
			{

				getline(filename,line);
				boost::char_separator<char> sep("	");
				tokenizer tok(line, sep);
				tokenizer::iterator  it=tok.begin();

				for (i=0;i<nCol;i++){
					token = *it;
					classExp.push_back(token);
					it++;
				}
			}
		}

		out.close();
}

/* This function decides if the input is an Microarray of Distance Matrix.
 * We will only concider the Mircoarrays for this program
 *
 * Input: filename
 * Output : none, readMicroarray(filename) will store the store the matrix
 * in a single dimensional global array input[]
 *
 */

void readInput(std::ifstream& filename, string filenameIn) {

		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
		getline(filename,line);
		boost::char_separator<char> sep("	,. \n");
		tokenizer tok(line, sep);
		tokenizer::iterator  it=tok.begin();
		token = *it;

		if (token.compare("<MicroarrayData>")==0){
			readMicroarray(filename);
			type = 1;
			cout<<"\nType = Microarray\n";
		}

		else if (token.compare("<DistanceData>") == 0){
		//	readDistance(filename);
			type = 2;
			cout<<"\nType = DistanceData\n";
		}

		else
			cout<<"Data not defined";
	}

/* Main function */

int main(int argc, char *argv[])
{

	filenameIn = argv[1];
	cout<<"File Name (input)= "<<filenameIn<<std::endl;

	std::ifstream inputStream (filenameIn.c_str());

	filenameOutS = filenameIn + ".knn";
	filenameNode = filenameIn + ".node";

	filenameOut = new char[filenameOutS.size()+1];
	filenameOut[filenameOutS.size()]=0;
	memcpy(filenameOut,filenameOutS.c_str(),filenameOutS.size());

	time(&now);
	current = localtime(&now);

	printf("\nCalculation starts at time : %i:%i:%i\n", current->tm_hour, current->tm_min, current->tm_sec);

	readInput(inputStream, filenameIn);
	knn(filenameOut, Ina, nRow, nCol, chunkSize, k);

	time(&now);
	current = localtime(&now);
	printf("\nOutput stored at time : %i:%i:%i\n", current->tm_hour, current->tm_min, current->tm_sec);

	return 0;
}


// Copyright Louis Dionne 2013-2016
// 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_TEST_LAWS_LOGICAL_HPP
#define BOOST_HANA_TEST_LAWS_LOGICAL_HPP

#include <boost/hana/assert.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/concept/comparable.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/functional/capture.hpp>
#include <boost/hana/lazy.hpp>
#include <boost/hana/concept/logical.hpp>

#include <laws/base.hpp>

namespace boost { namespace hana { namespace test {
    template <typename L, typename = when<true>>
    struct TestLogical : TestLogical<L, laws> {
        using TestLogical<L, laws>::TestLogical;
    };

    template <typename L>
    struct TestLogical<L, laws> {
        template <typename Xs, typename Pred, typename F>
        static void for_each_such_that(Xs xs, Pred pred, F f) {
            hana::for_each(xs, [&pred, &f](auto x) {
                hana::eval_if(pred(x),
                    hana::make_lazy(f)(x),
                    [](auto) { }
                );
            });
        }

        template <typename Xs>
        TestLogical(Xs xs) {
            hana::for_each(xs, [](auto x) {
                static_assert(Logical<decltype(x)>::value, "");
            });

            foreach3(xs, hana::capture(xs)([](auto xs, auto a, auto b, auto c) {
                auto true_valued = [](auto x) {
                    return hana::if_(x, true_c, false_c);
                };
                auto false_valued = [](auto x) {
                    return hana::if_(x, false_c, true_c);
                };

                // associativity
                BOOST_HANA_CHECK(hana::equal(
                    hana::or_(a, hana::or_(b, c)),
                    hana::or_(hana::or_(a, b), c)
                ));
                BOOST_HANA_CHECK(hana::equal(
                    hana::and_(a, hana::and_(b, c)),
                    hana::and_(hana::and_(a, b), c)
                ));

                // equivalence through commutativity
                BOOST_HANA_CHECK(
                    hana::or_(a, b) ^iff^ hana::or_(b, a)
                );
                BOOST_HANA_CHECK(
                    hana::and_(a, b) ^iff^ hana::and_(b, a)
                );

                // absorption
                BOOST_HANA_CHECK(hana::equal(
                    hana::or_(a, hana::and_(a, b)), a
                ));
                BOOST_HANA_CHECK(hana::equal(
                    hana::and_(a, hana::or_(a, b)), a
                ));

                // left identity
                TestLogical::for_each_such_that(xs, true_valued,
                hana::capture(a)([](auto a, auto t) {
                    BOOST_HANA_CHECK(hana::equal(
                        hana::and_(t, a), a
                    ));
                }));
                TestLogical::for_each_such_that(xs, false_valued,
                hana::capture(a)([](auto a, auto f) {
                    BOOST_HANA_CHECK(hana::equal(
                        hana::or_(f, a), a
                    ));
                }));

                // distributivity
                BOOST_HANA_CHECK(hana::equal(
                    hana::or_(a, hana::and_(b, c)),
                    hana::and_(hana::or_(a, b), hana::or_(a, c))
                ));
                BOOST_HANA_CHECK(hana::equal(
                    hana::and_(a, hana::or_(b, c)),
                    hana::or_(hana::and_(a, b), hana::and_(a, c))
                ));

                // complements
                BOOST_HANA_CHECK(true_valued(hana::or_(a, hana::not_(a))));
                BOOST_HANA_CHECK(false_valued(hana::and_(a, hana::not_(a))));

            }));
        }
    };

    template <typename C>
    struct TestLogical<C, when<Constant<C>::value>>
        : TestLogical<C, laws>
    {
        template <typename Xs>
        TestLogical(Xs xs) : TestLogical<C, laws>{xs} {
            foreach2(xs, [](auto x, auto y) {

                BOOST_HANA_CHECK(hana::equal(
                    hana::value(hana::not_(x)),
                    hana::not_(hana::value(x))
                ));

                BOOST_HANA_CHECK(hana::equal(
                    hana::value(hana::and_(x, y)),
                    hana::and_(hana::value(x), hana::value(y))
                ));

                BOOST_HANA_CHECK(hana::equal(
                    hana::value(hana::or_(x, y)),
                    hana::or_(hana::value(x), hana::value(y))
                ));

            });
        }
    };
}}} // end namespace boost::hana::test

#endif // !BOOST_HANA_TEST_LAWS_LOGICAL_HPP


//  Copyright (c) 2018-2019 Cem Bassoy
//
//  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)
//
//  The authors gratefully acknowledge the support of
//  Fraunhofer and Google in producing this work
//  which started as a Google Summer of Code project.
//

#include <iostream>
#include <random>
#include <boost/numeric/ublas/tensor.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/test/unit_test.hpp>

#include "utility.hpp"

// BOOST_AUTO_TEST_SUITE ( test_tensor_matrix_interoperability, * boost::unit_test::depends_on("test_tensor") ) ;

BOOST_AUTO_TEST_SUITE ( test_tensor_matrix_interoperability )

using test_types = zip<int,long,float,double>::with_t<boost::numeric::ublas::first_order, boost::numeric::ublas::last_order>;

BOOST_AUTO_TEST_CASE_TEMPLATE( test_tensor_matrix_copy_ctor, value,  test_types)
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;

	tensor_type a1 = matrix_type();
	BOOST_CHECK_EQUAL( a1.size() , 0ul );
	BOOST_CHECK( a1.empty() );
	BOOST_CHECK_EQUAL( a1.data() , nullptr);

	tensor_type a2 = matrix_type(1,1);
	BOOST_CHECK_EQUAL(  a2.size() , 1 );
	BOOST_CHECK( !a2.empty() );
	BOOST_CHECK_NE(  a2.data() , nullptr);

	tensor_type a3 = matrix_type(2,1);
	BOOST_CHECK_EQUAL(  a3.size() , 2 );
	BOOST_CHECK( !a3.empty() );
	BOOST_CHECK_NE(  a3.data() , nullptr);

	tensor_type a4 = matrix_type(1,2);
	BOOST_CHECK_EQUAL(  a4.size() , 2 );
	BOOST_CHECK( !a4.empty() );
	BOOST_CHECK_NE(  a4.data() , nullptr);

	tensor_type a5 = matrix_type(2,3);
	BOOST_CHECK_EQUAL(  a5.size() , 6 );
	BOOST_CHECK( !a5.empty() );
	BOOST_CHECK_NE(  a5.data() , nullptr);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( test_tensor_vector_copy_ctor, value,  test_types)
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using vector_type = typename tensor_type::vector_type;

	tensor_type a1 = vector_type();
	BOOST_CHECK_EQUAL( a1.size() , 0ul );
	BOOST_CHECK( a1.empty() );
	BOOST_CHECK_EQUAL( a1.data() , nullptr);

	tensor_type a2 = vector_type(1);
	BOOST_CHECK_EQUAL(  a2.size() , 1 );
	BOOST_CHECK( !a2.empty() );
	BOOST_CHECK_NE(  a2.data() , nullptr);

	tensor_type a3 = vector_type(2);
	BOOST_CHECK_EQUAL(  a3.size() , 2 );
	BOOST_CHECK( !a3.empty() );
	BOOST_CHECK_NE(  a3.data() , nullptr);

	tensor_type a4 = vector_type(2);
	BOOST_CHECK_EQUAL(  a4.size() , 2 );
	BOOST_CHECK( !a4.empty() );
	BOOST_CHECK_NE(  a4.data() , nullptr);

	tensor_type a5 = vector_type(3);
	BOOST_CHECK_EQUAL(  a5.size() , 3 );
	BOOST_CHECK( !a5.empty() );
	BOOST_CHECK_NE(  a5.data() , nullptr);
}

struct fixture
{
	using extents_type = boost::numeric::ublas::basic_extents<std::size_t>;
	fixture()
	  : extents{
				extents_type{1,1},  // 1
				extents_type{1,2},  // 2
				extents_type{2,1},  // 3
				extents_type{2,3},  // 4
				extents_type{9,7},  // 5
				extents_type{9,11}, // 6
				extents_type{12,12}, // 7
	      extents_type{15,17}} // 8
	{
	}
	std::vector<extents_type> extents;
};

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_matrix_copy_ctor_extents, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;

	auto check = [](auto const& e) {
		assert(e.size()==2);
		tensor_type t = matrix_type{e[0],e[1]};
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_vector_copy_ctor_extents, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using vector_type = typename tensor_type::vector_type;

	auto check = [](auto const& e) {
		assert(e.size()==2);
		if(e.empty())
			return;

		tensor_type t = vector_type(e.product());
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_matrix_copy_assignment, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = matrix_type(e[0],e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		t = r;

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0) );
		BOOST_CHECK_EQUAL (  t.extents().at(1) , e.at(1) );
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		for(auto j = 0ul; j < t.size(1); ++j){
			for(auto i = 0ul; i < t.size(0); ++i){
				BOOST_CHECK_EQUAL( t.at(i,j), r(i,j)  );
			}
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_vector_copy_assignment, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using vector_type = typename tensor_type::vector_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = vector_type(e[0]*e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		t = r;

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0)*e.at(1) );
		BOOST_CHECK_EQUAL (  t.extents().at(1) , 1);
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		for(auto i = 0ul; i < t.size(); ++i){
			BOOST_CHECK_EQUAL( t[i], r(i)  );
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_matrix_move_assignment, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = matrix_type(e[0],e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		auto q = r;
		t = std::move(r);

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0) );
		BOOST_CHECK_EQUAL (  t.extents().at(1) , e.at(1) );
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		for(auto j = 0ul; j < t.size(1); ++j){
			for(auto i = 0ul; i < t.size(0); ++i){
				BOOST_CHECK_EQUAL( t.at(i,j), q(i,j)  );
			}
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_vector_move_assignment, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using vector_type = typename tensor_type::vector_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = vector_type(e[0]*e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		auto q = r;
		t = std::move(r);

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0) * e.at(1));
		BOOST_CHECK_EQUAL (  t.extents().at(1) , 1);
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		for(auto i = 0ul; i < t.size(); ++i){
			BOOST_CHECK_EQUAL( t[i], q(i)  );
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_matrix_expressions, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = matrix_type(e[0],e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		t = r + 3*r;
		tensor_type s = r + 3*r;
		tensor_type q = s + r + 3*r + s; // + 3*r

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0) );
		BOOST_CHECK_EQUAL (  t.extents().at(1) , e.at(1) );
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		BOOST_CHECK_EQUAL (  s.extents().at(0) , e.at(0) );
		BOOST_CHECK_EQUAL (  s.extents().at(1) , e.at(1) );
		BOOST_CHECK_EQUAL (  s.size() , e.product() );
		BOOST_CHECK_EQUAL (  s.rank() , e.size() );
		BOOST_CHECK       ( !s.empty()    );
		BOOST_CHECK_NE    (  s.data() , nullptr);

		BOOST_CHECK_EQUAL (  q.extents().at(0) , e.at(0) );
		BOOST_CHECK_EQUAL (  q.extents().at(1) , e.at(1) );
		BOOST_CHECK_EQUAL (  q.size() , e.product() );
		BOOST_CHECK_EQUAL (  q.rank() , e.size() );
		BOOST_CHECK       ( !q.empty()    );
		BOOST_CHECK_NE    (  q.data() , nullptr);

		for(auto j = 0ul; j < t.size(1); ++j){
			for(auto i = 0ul; i < t.size(0); ++i){
				BOOST_CHECK_EQUAL( t.at(i,j), 4*r(i,j)  );
				BOOST_CHECK_EQUAL( s.at(i,j), t.at(i,j)  );
				BOOST_CHECK_EQUAL( q.at(i,j), 3*s.at(i,j)  );
			}
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_vector_expressions, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using vector_type = typename tensor_type::vector_type;

	auto check = [](auto const& e)
	{
		assert(e.size() == 2);
		auto t = tensor_type{};
		auto r = vector_type(e[0]*e[1]);
		std::iota(r.data().begin(),r.data().end(), 1);
		t = r + 3*r;
		tensor_type s = r + 3*r;
		tensor_type q = s + r + 3*r + s; // + 3*r

		BOOST_CHECK_EQUAL (  t.extents().at(0) , e.at(0)*e.at(1) );
		BOOST_CHECK_EQUAL (  t.extents().at(1) , 1);
		BOOST_CHECK_EQUAL (  t.size() , e.product() );
		BOOST_CHECK_EQUAL (  t.rank() , e.size() );
		BOOST_CHECK       ( !t.empty()    );
		BOOST_CHECK_NE    (  t.data() , nullptr);

		BOOST_CHECK_EQUAL (  s.extents().at(0) , e.at(0)*e.at(1) );
		BOOST_CHECK_EQUAL (  s.extents().at(1) , 1);
		BOOST_CHECK_EQUAL (  s.size() , e.product() );
		BOOST_CHECK_EQUAL (  s.rank() , e.size() );
		BOOST_CHECK       ( !s.empty()    );
		BOOST_CHECK_NE    (  s.data() , nullptr);

		BOOST_CHECK_EQUAL (  q.extents().at(0) , e.at(0)*e.at(1) );
		BOOST_CHECK_EQUAL (  q.extents().at(1) , 1);
		BOOST_CHECK_EQUAL (  q.size() , e.product() );
		BOOST_CHECK_EQUAL (  q.rank() , e.size() );
		BOOST_CHECK       ( !q.empty()    );
		BOOST_CHECK_NE    (  q.data() , nullptr);

		for(auto i = 0ul; i < t.size(); ++i){
			BOOST_CHECK_EQUAL( t.at(i), 4*r(i)  );
			BOOST_CHECK_EQUAL( s.at(i), t.at(i)  );
			BOOST_CHECK_EQUAL( q.at(i), 3*s.at(i)  );
		}
	};

	for(auto const& e : extents)
		check(e);
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_matrix_vector_expressions, value,  test_types, fixture )
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using matrix_type = typename tensor_type::matrix_type;
	using vector_type = typename tensor_type::vector_type;

	auto check = [](auto const& e)
	{
		if(e.product() <= 2)
			return;
		assert(e.size() == 2);
		auto Q = tensor_type{e[0],1};
		auto A = matrix_type(e[0],e[1]);
		auto b = vector_type(e[1]);
		auto c = vector_type(e[0]);
		std::iota(b.data().begin(),b.data().end(), 1);
		std::fill(A.data().begin(),A.data().end(), 1);
		std::fill(c.data().begin(),c.data().end(), 2);
		std::fill(Q.begin(),Q.end(), 2);

		tensor_type T = Q + (ublas::prod(A , b) + 2*c) + 3*Q;

		BOOST_CHECK_EQUAL (  T.extents().at(0) , Q.extents().at(0) );
		BOOST_CHECK_EQUAL (  T.extents().at(1) , Q.extents().at(1));
		BOOST_CHECK_EQUAL (  T.size() , Q.size() );
		BOOST_CHECK_EQUAL (  T.size() , c.size() );
		BOOST_CHECK_EQUAL (  T.rank() , Q.rank() );
		BOOST_CHECK       ( !T.empty()    );
		BOOST_CHECK_NE    (  T.data() , nullptr);

		for(auto i = 0ul; i < T.size(); ++i){
			auto n = e[1];
			auto ab = n * (n+1) / 2;
			BOOST_CHECK_EQUAL( T(i), ab+4*Q(0)+2*c(0)  );
		}

	};

	for(auto const& e : extents)
		check(e);
}

BOOST_AUTO_TEST_SUITE_END()