// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2007-2014 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2014 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2014 Mateusz Loskot, London, UK.
// Copyright (c) 2013-2014 Adam Wulkiewicz, Lodz, Poland.

// This file was modified by Oracle on 2014, 2017.
// Modifications copyright (c) 2014-2017, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to 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)

#include <sstream>

#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_same.hpp>
#include <geometry_test_common.hpp>

#include <boost/geometry/algorithms/comparable_distance.hpp>
#include <boost/geometry/algorithms/make.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/io/wkt/read.hpp>
#include <boost/geometry/strategies/strategies.hpp>

template <typename P>
void test_distance_result()
{
    typedef typename bg::default_distance_result<P, P>::type distance_type;

    P p1 = bg::make<P>(0, 0);
    P p2 = bg::make<P>(3, 0);
    P p3 = bg::make<P>(0, 4);

    distance_type dr12 = bg::comparable_distance(p1, p2);
    distance_type dr13 = bg::comparable_distance(p1, p3);
    distance_type dr23 = bg::comparable_distance(p2, p3);

    BOOST_CHECK_CLOSE(dr12, 9.000, 0.001);
    BOOST_CHECK_CLOSE(dr13, 16.000, 0.001);
    BOOST_CHECK_CLOSE(dr23, 25.000, 0.001);

}

template <typename P>
void test_distance_point()
{
    P p1;
    bg::set<0>(p1, 1);
    bg::set<1>(p1, 1);

    P p2;
    bg::set<0>(p2, 2);
    bg::set<1>(p2, 2);

    typename bg::coordinate_type<P>::type d = bg::comparable_distance(p1, p2);
    BOOST_CHECK_CLOSE(d, 2.0, 0.001);
}

template <typename P>
void test_distance_segment()
{
    typedef typename bg::coordinate_type<P>::type coordinate_type;

    P s1 = bg::make<P>(2, 2);
    P s2 = bg::make<P>(3, 3);

    // Check points left, right, projected-left, projected-right, on segment
    P p1 = bg::make<P>(0, 0);
    P p2 = bg::make<P>(4, 4);
    P p3 = bg::make<P>(2.4, 2.6);
    P p4 = bg::make<P>(2.6, 2.4);
    P p5 = bg::make<P>(2.5, 2.5);

    bg::model::referring_segment<P const> const seg(s1, s2);

    coordinate_type d1 = bg::comparable_distance(p1, seg); BOOST_CHECK_CLOSE(d1, 8.0, 0.001);
    coordinate_type d2 = bg::comparable_distance(p2, seg); BOOST_CHECK_CLOSE(d2, 2.0, 0.001);
    coordinate_type d3 = bg::comparable_distance(p3, seg); BOOST_CHECK_CLOSE(d3, 0.02, 0.001);
    coordinate_type d4 = bg::comparable_distance(p4, seg); BOOST_CHECK_CLOSE(d4, 0.02, 0.001);
    coordinate_type d5 = bg::comparable_distance(p5, seg); BOOST_CHECK_CLOSE(d5, 0.0, 0.001);

    // Reverse case
    coordinate_type dr1 = bg::comparable_distance(seg, p1); BOOST_CHECK_CLOSE(dr1, d1, 0.001);
    coordinate_type dr2 = bg::comparable_distance(seg, p2); BOOST_CHECK_CLOSE(dr2, d2, 0.001);
}

template <typename P>
void test_distance_linestring()
{
    bg::model::linestring<P> points;
    points.push_back(bg::make<P>(1, 1));
    points.push_back(bg::make<P>(3, 3));

    P p = bg::make<P>(2, 1);

    typename bg::coordinate_type<P>::type d = bg::comparable_distance(p, points);
    BOOST_CHECK_CLOSE(d, 0.5, 0.001);

    p = bg::make<P>(5, 5);
    d = bg::comparable_distance(p, points);
    BOOST_CHECK_CLOSE(d, 8.0, 0.001);

    bg::model::linestring<P> line;
    line.push_back(bg::make<P>(1,1));
    line.push_back(bg::make<P>(2,2));
    line.push_back(bg::make<P>(3,3));

    p = bg::make<P>(5, 5);

    d = bg::comparable_distance(p, line);
    BOOST_CHECK_CLOSE(d, 8.0, 0.001);

    // Reverse case
    d = bg::comparable_distance(line, p);
    BOOST_CHECK_CLOSE(d, 8.0, 0.001);
}

template <typename P>
void test_all()
{
    test_distance_result<P>();
    test_distance_point<P>();
    test_distance_segment<P>();
    test_distance_linestring<P>();
}

template <typename T>
void test_double_result_from_integer()
{
    typedef bg::model::point<T, 2, bg::cs::cartesian> point_type;

    point_type point;

    // Check linestring
    bg::model::linestring<point_type> linestring;
    bg::read_wkt("POINT(2 2)", point);
    bg::read_wkt("LINESTRING(4 1,1 4)", linestring);

    double normal_distance = bg::distance(point, linestring);
    double comparable_distance = bg::comparable_distance(point, linestring);

    BOOST_CHECK_CLOSE(normal_distance, std::sqrt(0.5), 0.001);
    BOOST_CHECK_CLOSE(comparable_distance, 0.5, 0.001);

    // Check polygon
    bg::model::polygon<point_type> polygon;
    bg::read_wkt("POLYGON((0 0,1 9,8 1,0 0),(1 1,4 1,1 4,1 1))", polygon);

    normal_distance = bg::distance(point, polygon);
    comparable_distance = bg::comparable_distance(point, polygon);

    BOOST_CHECK_CLOSE(normal_distance, std::sqrt(0.5), 0.001);
    BOOST_CHECK_CLOSE(comparable_distance, 0.5, 0.001);
}

template <typename T>
struct test_variant_different_default_strategy
{
    static inline void apply()
    {
        typedef bg::model::point<T, 2, bg::cs::cartesian> point_type;
        typedef bg::model::segment<point_type> segment_type;
        typedef bg::model::box<point_type> box_type;
        typedef boost::variant<point_type, segment_type, box_type> variant_type;

        point_type point;
        bg::read_wkt("POINT(1 3)", point);

        segment_type seg;
        bg::read_wkt("LINESTRING(1 1,4 4)", seg);

        box_type box;
        bg::read_wkt("BOX(-1 -1,0 0)", box);

        variant_type v1, v2;

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            variant_type, variant_type, bg::default_strategy
                        >::type,
                    typename bg::comparable_distance_result
                        <
                            point_type, point_type, bg::default_strategy
                        >::type
                >
        ));

        // Default strategy
        v1 = point;
        v2 = point;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, point),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, point),
                          bg::comparable_distance(point, point),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, point),
                          0.0001);
        v1 = point;
        v2 = seg;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, seg),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, seg),
                          bg::comparable_distance(point, seg),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, seg), 0.0001);
        v1 = point;
        v2 = box;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, box),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, box),
                          bg::comparable_distance(point, box),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, box), 0.0001);
    }
};

template <typename T, typename ExpectedResultType = double>
struct test_variant_same_default_strategy
{
    static inline void apply()
    {
        typedef bg::model::point<T, 2, bg::cs::cartesian> point_type;
        typedef bg::model::segment<point_type> segment_type;
        typedef bg::model::linestring<point_type> linestring_type;
        typedef boost::variant
            <
                point_type, segment_type, linestring_type
            > variant_type;

        point_type point;
        bg::read_wkt("POINT(1 3)", point);

        segment_type seg;
        bg::read_wkt("LINESTRING(1 1,4 4)", seg);

        linestring_type linestring;
        bg::read_wkt("LINESTRING(-1 -1,-1 0,0 0,0 -1,-1 -1)", linestring);

        variant_type v1, v2;

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            variant_type, variant_type, bg::default_strategy
                        >::type,
                    ExpectedResultType
                >
        ));

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            point_type, point_type, bg::default_strategy
                        >::type,
                    ExpectedResultType
                >
        ));

        // Default strategy
        v1 = point;
        v2 = point;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, point),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, point),
                          bg::comparable_distance(point, point),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, point),
                          0.0001);
        v1 = point;
        v2 = seg;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, seg),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, seg),
                          bg::comparable_distance(point, seg),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, seg),
                          0.0001);
        v1 = point;
        v2 = linestring;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2),
                          bg::comparable_distance(point, linestring),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, linestring),
                          bg::comparable_distance(point, linestring),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(point, v2),
                          bg::comparable_distance(point, linestring),
                          0.0001);
    }
};

template <typename T, typename ExpectedResultType = T>
struct test_variant_with_strategy
{
    static inline void apply()
    {
        typedef bg::strategy::distance::projected_point<T> strategy_type;

        typedef bg::model::point<T, 2, bg::cs::cartesian> point_type;
        typedef bg::model::segment<point_type> segment_type;
        typedef bg::model::linestring<point_type> linestring_type;
        typedef bg::model::multi_linestring
            <
                linestring_type
            > multi_linestring_type;
        typedef boost::variant
            <
                segment_type, linestring_type, multi_linestring_type
            > variant_type;

        segment_type seg;
        bg::read_wkt("LINESTRING(1 1,4 4)", seg);

        linestring_type ls;
        bg::read_wkt("LINESTRING(-1 -1,-1 0,0 0,0 -1,-1 -1)", ls);

        multi_linestring_type mls;
        bg::read_wkt("MULTILINESTRING((10 0,20 0),(30 0,40 0))", mls);

        variant_type v1, v2;

        strategy_type strategy;

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            variant_type, variant_type, strategy_type
                        >::type,
                    ExpectedResultType
                >
        ));

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            segment_type, linestring_type, strategy_type
                        >::type,
                    ExpectedResultType
                >
        ));

        // Passed strategy
        v1 = seg;
        v2 = seg;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2, strategy),
                          bg::comparable_distance(seg, seg, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, seg, strategy),
                          bg::comparable_distance(seg, seg, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(seg, v2, strategy),
                          bg::comparable_distance(seg, seg, strategy),
                          0.0001);
        v1 = seg;
        v2 = ls;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2, strategy),
                          bg::comparable_distance(seg, ls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, ls, strategy),
                          bg::comparable_distance(seg, ls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(seg, v2, strategy),
                          bg::comparable_distance(seg, ls, strategy),
                          0.0001);
        v1 = seg;
        v2 = mls;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2, strategy),
                          bg::comparable_distance(seg, mls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, mls, strategy),
                          bg::comparable_distance(seg, mls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(seg, v2, strategy),
                          bg::comparable_distance(seg, mls, strategy),
                          0.0001);
        v1 = ls;
        v2 = mls;
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, v2, strategy),
                          bg::comparable_distance(ls, mls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(v1, mls, strategy),
                          bg::comparable_distance(ls, mls, strategy),
                          0.0001);
        BOOST_CHECK_CLOSE(bg::comparable_distance(ls, v2, strategy),
                          bg::comparable_distance(ls, mls, strategy),
                          0.0001);
    }
};

template <typename T, bool IsIntergral = boost::is_integral<T>::value>
struct check_result
{
    template <typename ExpectedResult>
    static inline void apply(T const& value,
                             ExpectedResult const& expected_value)
    {
        BOOST_CHECK_EQUAL(value, expected_value);
    }
};

template <typename T>
struct check_result<T, false>
{
    template <typename ExpectedResult>
    static inline void apply(T const& value,
                             ExpectedResult const& expected_value)
    {
        BOOST_CHECK_CLOSE(value, expected_value, 0.0001);
    }
};

template <typename T>
struct test_variant_boxes
{
    static inline void apply()
    {
        typedef bg::model::point<T, 2, bg::cs::cartesian> point_type;
        typedef bg::model::box<point_type> box_type;
        typedef boost::variant<box_type> variant_type;

        box_type box1, box2;
        bg::read_wkt("BOX(-1 -1,0 0)", box1);
        bg::read_wkt("BOX(1 1,2 2)", box2);

        variant_type v1 = box1, v2 = box2;

        typedef typename boost::mpl::if_c
            <
                boost::is_float<T>::value,
                double,
                typename bg::util::detail::default_integral::type
            >::type expected_result_type;

        BOOST_MPL_ASSERT((
            boost::is_same
                <
                    typename bg::comparable_distance_result
                        <
                            variant_type, variant_type, bg::default_strategy
                        >::type,
                    expected_result_type
                >
        ));

        // Default strategy
        check_result<T>::apply(bg::comparable_distance(v1, v2),
                               bg::comparable_distance(box1, box2));
        check_result<T>::apply(bg::comparable_distance(v1, box2),
                               bg::comparable_distance(box1, box2));
        check_result<T>::apply(bg::comparable_distance(box1, v2),
                               bg::comparable_distance(box1, box2));
    }
};

int test_main(int, char* [])
{
    test_double_result_from_integer<int>();
    test_double_result_from_integer<boost::long_long_type>();

    test_all<bg::model::d2::point_xy<float> >();
    test_all<bg::model::d2::point_xy<double> >();

#ifdef HAVE_TTMATH
    test_all<bg::model::d2::point_xy<ttmath_big> >();
#endif

    // test variant support
    test_variant_different_default_strategy<double>::apply();

    test_variant_same_default_strategy<double>::apply();
    test_variant_same_default_strategy<int>::apply();
    test_variant_same_default_strategy<long>::apply();

    test_variant_with_strategy<double>::apply();
    test_variant_with_strategy<float>::apply();
    test_variant_with_strategy<long double>::apply();
    test_variant_with_strategy<int, double>::apply();
#ifdef HAVE_TTMATH
    test_variant_with_strategy<ttmath_big>::apply();
#endif

    test_variant_boxes<double>::apply();
    test_variant_boxes<int>::apply();
    test_variant_boxes<long>::apply();

    return 0;
}


/* ScaFES
 * Copyright (c) 2011-2015, ZIH, TU Dresden, Federal Republic of Germany.
 * For details, see the files COPYING and LICENSE in the base directory
 * of the package.
 */

/**
 *  @file ScaFES_Grid.hpp
 *  @brief Contains the class template Grid.
 */

#ifndef SCAFES_GRID_HPP_
#define SCAFES_GRID_HPP_

#include "ScaFES_Config.hpp"

#include <iterator>
#include <vector>

#ifdef SCAFES_HAVE_BOOST
#include <boost/version.hpp>
#endif

#ifdef SCAFES_HAVE_BOOST_SERIALIZATION
// Forward declaration.
namespace boost
{
namespace serialization
{
    class access;
}
}
#include <boost/serialization/version.hpp>
#if BOOST_VERSION < 105900
#include <boost/serialization/pfto.hpp>
#endif
#include <boost/serialization/vector.hpp>

#include <boost/config/suffix.hpp>
#endif

#include "ScaFES_Ntuple.hpp"
#include "ScaFES_Ntuple_FreeFunctions.hpp"

namespace ScaFES
{

/*******************************************************************************
 ******************************************************************************/
/**
* \class Grid
* @brief The class template \c Grid represents a structured grid
* of dimension \c DIM, where \c DIM is the template parameter of the class
* template. A grid is a \c DIM - dimensional hypercuboid.
*
* The grid nodes are numbered in two different ways:
* <ul>
* <li> globally, i.e. all grid nodes of the WHOLE MPI domain are numbered
*      lexicographically, </li>
* <li> locally, i.e. all grid nodes of ONE MPI partition are numbered
*      lexicographically. </li>
* </ul>
*
* As the nodes of the grid are lexicographically numbered, the first and
* the last node number of the grid together with its corresponding coordinates
* have to be stored, only.
*
* Due to the lexicographical node numbering, the coordinates of all nodes
* can be easily computed. For this purpose, the method \c coordinates() was
* provided.
*
* Furthermore, the class contains an iterator class. As the iterator is
* designed like an STL iterator, it can be applied completely analogously.

* The most important components of the inner class \c Iterator are the member
* methods \c idxNode() and \c idxScalarNode(). The first one returns
* the current node number in tuple notation, the second on returns
* the current node number as a scalar number.
*
* Numbering of grid nodes for DIM = 3 of a grid with n^3 nodes:
* Gap in y-direction: n     (1, n+1, 2*n+1, 3*n+1,...)
* Gap in z-direction: n^2   (n^2, 2*n^2, 3*n^2,....)
*        *-----------------* n^3
*       /                 /|
*      /                 / |
*     /                 /  |
*    /     (n-1)*n^2+n /   |
*    *----------------*    |
*    |                |    * n^2
*    |                |   /
*    |                |  /
*    |                | /
*    |                |/
*  1 *----------------* n
*
* \n Numbering of grid faces for DIM = 3:
*                            ^          ^
*                            |         / Back = 3
*                            | Top = 4/
*                                    /
*                     *-----------------*
*                    /                 /|
*                   /                 / |
*                  /                 /  |
*                 /                 /   |
*                *-----------------*    |
*                |                 |    *
*       <------  |                 |   /   ------->
*        Left=1  |                 |  /    Right = 0
*                |                 | /
*                |                 |/
*                *-----------------*
* Right:  direction = 0
* Left:   direction = 1      | Bottom = 5
* Top:    direction = 2      |
* Bottom: direction = 3      |
* Front:  direction = 4      v
* Back:   direction = 5
*/
template <std::size_t DIM = 3> class Grid
{
public:
    /*----------------------------------------------------------------------
    | INTERNAL CLASSES.
    ----------------------------------------------------------------------*/
    /** Class for iterating through the grid.*/
    class Iterator : public std::iterator<std::random_access_iterator_tag,
                                          ScaFES::Ntuple<int, DIM>>
    {
    public:
        /*--------------------------------------------------------------
        | LIFE CYCLE METHODS.
        --------------------------------------------------------------*/
        /** Creates own constructor.
         * \param gridIter Given grid over which will be iterated.
         * \param idxNode Iteration will start from this node.
         */
        Iterator(const Grid<DIM>* gridIter,
                 const ScaFES::Ntuple<int, DIM>& idxNode);

        /*--------------------------------------------------------------
        | GETTER METHODS.
        --------------------------------------------------------------*/
        /** Returns the index of the current node. */
        const ScaFES::Ntuple<int, DIM>& idxNode() const;

        /** Returns the scalar index of the current node. */
        const unsigned long int& idxScalarNode() const;

        /*--------------------------------------------------------------
        | COMPARISON METHODS.
        --------------------------------------------------------------*/
        /** Checks if the local node number of this iterator is unequal
         * to the one of the given iterator. */
        bool operator!=(const Iterator& other) const;

        /** Checks if the local node number of this iterator is smaller
         * than the one of the given iterator. */
        bool operator<(const Iterator& other) const;

        /*--------------------------------------------------------------
        | WORK METHODS.
        --------------------------------------------------------------*/
        /** Returns the index of the current node. */
        const ScaFES::Ntuple<int, DIM>& operator*();

        /** Increases the current local and the current global
         * node number.
         * \remarks Prefix variant.
         */
        Iterator& operator++();

        /** Increases the current local and the current global
         * node number.
         * \remarks Postfix variant.
         */
        Iterator operator++(int /*incr*/);

    private:
        /*--------------------------------------------------------------
        | MEMBER VARIABLES.
        --------------------------------------------------------------*/
        /** Grid over which should be iterated. */
        const Grid<DIM>* mGridIter;

        /** Index of current node. */
        ScaFES::Ntuple<int, DIM> mIdxNode;

        /** Scalar index of current node. */
        unsigned long int mIdxScalarNode;

        /*--------------------------------------------------------------
        | INTERNAL WORK METHODS.
        --------------------------------------------------------------*/
        /** Increases the current local and the current global node
         * number iterating "columnwise" through the grid.
         */
        Iterator& nextStyleC();

        /** Increases the current local and the current global node
         * number iterating "rowwise" through the grid.
         */
        Iterator& nextStyleFortran();

        /*--------------------------------------------------------------
        | FRIEND CLASSES.
        --------------------------------------------------------------*/
        friend class Grid<DIM>;
    };

    /*----------------------------------------------------------------------
    | TYPE DEFINITIONS.
    ----------------------------------------------------------------------*/
    /** Type definition for iterator class analogously to STL class. */
    typedef Iterator iterator;

    /** Type definition for iterator class analogously to STL class. */
    typedef const Iterator const_iterator;

    /** Type definition for iterator class analogously to STL class. */
    typedef ScaFES::Ntuple<int, DIM>& reference_type;

    /*----------------------------------------------------------------------
    | FRIEND CLASSES.
    ----------------------------------------------------------------------*/
#ifdef SCAFES_HAVE_BOOST_SERIALIZATION
    friend class boost::serialization::access;
#endif

    /*----------------------------------------------------------------------
    | LIFE CYCLE METHODS.
    ----------------------------------------------------------------------*/
    /** Creates the default constructor. */
    Grid();

    /** Creates a new grid with given first global node number and
     * given last global node together with its corresponding coordinates.
     *  TODO: Reason for pass-by-value of coordinates?! [KF], 2015-11-17
     * \param idxNodeFirst Index of first node.
     * \param idxNodeLast Index of last node.
     * \param coordNodeFirst Coordinates of first node.
     * \param coordNodeLast Coordinates of last node.
     */
    Grid(const ScaFES::Ntuple<int, DIM>& idxNodeFirst,
         const ScaFES::Ntuple<int, DIM>& idxNodeLast,
         ScaFES::Ntuple<double, DIM> coordNodeFirst,
         ScaFES::Ntuple<double, DIM> coordNodeLast);

    /** Creates own copy constructor. */
    Grid(const Grid<DIM>& rhs);

    /** Creates own assignment operator using the copy-and-swap idiom. */
    Grid& operator=(Grid<DIM> rhs);

    /** Creates own destructor. */
    ~Grid();

    /*----------------------------------------------------------------------
    | GETTER METHODS.
    ----------------------------------------------------------------------*/
    /** Returns the dimension of the grid. */
    std::size_t dimGrid() const;

    /** Returns the index of the first node. */
    const ScaFES::Ntuple<int, DIM>& idxNodeFirst() const;

    /** Returns the component \c idx of the index of the first node. */
    const int& idxNodeFirst(const int& idx) const;

    /** Returns the index of the last node. */
    const ScaFES::Ntuple<int, DIM>& idxNodeLast() const;

    /** Returns the component \c idx of the index of the last node. */
    const int& idxNodeLast(const int& idx) const;

    /** Returns the number of nodes of the grid in each direction. */
    const ScaFES::Ntuple<int, DIM>& nNodes() const;

    /** Returns the number of nodes of the grid in one given direction. */
    const int& nNodes(const int& idx) const;

    /** Returns the total number of nodes of the grid. */
    int nNodesTotal() const;

    /** Returns the coordinates of the first node. */
    const ScaFES::Ntuple<double, DIM>& coordNodeFirst() const;

    /** Returns the coordinates of the last node. */
    const ScaFES::Ntuple<double, DIM>& coordNodeLast() const;

    /** Returns the grid size in all directions. */
    const ScaFES::Ntuple<double, DIM>& gridsize() const;

    /** Returns the grid size in one given direction. */
    const double& gridsize(const int& idx) const;

    /** Returns if the grid is numbered in C style notation. */
    const bool& isNumberedStyleC() const;

    /*----------------------------------------------------------------------
    | COMPARISON METHODS.
    ----------------------------------------------------------------------*/
    /** Tests if two grids are equal. */
    bool operator==(const Grid<DIM>& rhs) const;

    /** Tests if two grids are not equal. */
    bool operator!=(const Grid<DIM>& rhs) const;

    /*----------------------------------------------------------------------
    | WORK METHODS.
    ----------------------------------------------------------------------*/
    /** Returns an iterator which will start with the first node of the
     * underlying grid. */
    iterator begin() const;

    /** Returns an iterator which will start with the last node of the
     * underlying grid. */
    iterator end() const;

    /** Computes the corresponding scalar node number of a given node number
     * of the grid.
     * \param idxNode ScaFES::Ntuple<int,DIM> of a given node.
     */
    unsigned long int
    idxNodeTuple2Scalar(const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding scalar node number of a given node number
     * of the grid.
     * \param idxNodeScalar Scalar index of a given node.
     * \param idxNode ScaFES::Ntuple<int,DIM> of a given node.
     */
    void idxNodeTuple2Scalar(unsigned long int& idxNodeScalar,
                             const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding node number from a given scalar
     * node number.
     * \param idxScalarNode Scalar index of a given node number.
     */
    ScaFES::Ntuple<int, DIM>
    idxNodeScalar2Tuple(const unsigned long int& idxScalarNode) const;

    /** Computes the corresponding node number from a given scalar
     * node number.
     * \param idxNode ScaFES::Ntuple<int,DIM> tuple of a given node.
     * \param idxScalarNode Scalar index of a given node number.
     */
    void idxNodeScalar2Tuple(ScaFES::Ntuple<int, DIM>& idxNode,
                             const unsigned long int& idxScalarNode) const;

    /** Computes the coordinates of a given node number of the grid.
     * \param idxNode Given node number
     */
    ScaFES::Ntuple<double, DIM>
    coordinates(const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Returns true if the first node number is equal or smaller the
     * the last node number, i.e. if the grid consists of at
     * least one node. */
    bool valid() const;

    /** Returns true if a given node number is inside the grid.
     */
    bool inside(const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Extends the underlying grid by a given border width into a
     * given direction and returns the new modified grid.
     * The coordinates of the extended grid will remain the same as before,
     * whereas the number of nodes within the same physical domain will
     * increases.
     * \param dir Direction to expand.
     * \param nLayers Number of layers to expand into the given dir.
     * \remarks The ordering of the direction is as given in the
     * description of this class.
     */
    Grid<DIM> extend(const int& dir, const int& nLayers);

    /** Returns the union of this grid and another given grid.
     * It will be assumed that both grids have the same grid sizes!
     * \return Grid gg(min(mIdxNodeFirst, rhs.idxNodeFirst),
     *                 max(mIdxNodeLast, rhs.idxNodeLast))
     */
    Grid<DIM> unionWith(const Grid<DIM>& rhs) const;

    /** Returns the intersection of this grid and another given grid.
     * It will be assumed that both grids have the same grid sizes!
     * \return Grid gg(max(mIdxNodeFirst, rhs.idxNodeFirst),
     *                 min(mIdxNodeLast, rhs.idxNodeLast))
     */
    Grid<DIM> intersectWith(const Grid<DIM>& rhs) const;

#ifdef SCAFES_HAVE_BOOST_SERIALIZATION
    /** Serializes the class. */
    template <class Archive>
    void serialize(Archive& ar, const unsigned int version);
#endif

    /** Returns all direct neighboured LOCAL node numbers
     * of a given node number. */
    Ntuple<unsigned long int, 2 * DIM>
    connect(const ScaFES::Ntuple<int, DIM>& idxNode);

    /** Determines the neighbour global node into a given direction
     * of a global node number. */
    ScaFES::Ntuple<int, DIM> neigh(const ScaFES::Ntuple<int, DIM>& idxNode,
                                   const int& dir) const;

    /*----------------------------------------------------------------------
    | FREE METHODS.
    ----------------------------------------------------------------------*/
    /** Swaps the members of two grids. */
    template <std::size_t SS>
    friend void swap(ScaFES::Grid<SS>& first, ScaFES::Grid<SS>& second);

    /** Overloads the output operator. */
    template <std::size_t SS>
    friend std::ostream& operator<<(std::ostream& output,
                                    const ScaFES::Grid<SS>& rhs);

protected:
    /*----------------------------------------------------------------------
    | MEMBER VARIABLES.
    ----------------------------------------------------------------------*/
    /** First node number of the grid. */
    ScaFES::Ntuple<int, DIM> mIdxNodeFirst;

    /** Last node number of the grid. */
    ScaFES::Ntuple<int, DIM> mIdxNodeLast;

    /** Number of nodes of the grid in all directions. */
    ScaFES::Ntuple<int, DIM> mNnodes;

    /** Coordinate of the first node number. */
    ScaFES::Ntuple<double, DIM> mCoordNodeFirst;

    /** Coordinate of the last node number. */
    ScaFES::Ntuple<double, DIM> mCoordNodeLast;

    /** Grid size in all directions. */
    ScaFES::Ntuple<double, DIM> mGridsize;

    /** Is grid numbered in C style notation (alternative: FORTRAN style).*/
    bool mIsNumberedStyleC;

private:
    /*----------------------------------------------------------------------
    | INTERNAL METHODS.
    ----------------------------------------------------------------------*/
    /** Computes the corresponding scalar node number of a given
     * node number of the grid using the C style.
     * \param idxNode Given node number
     */
    unsigned long int
    idxNodeTuple2ScalarStyleC(const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding scalar node number of a given
     * node number of the grid using the C style.
     * \param idxScalarNode Corresponding scalar node number (return value).
     * \param idxNode Given node number.
     */
    void
    idxNodeTuple2ScalarStyleC(unsigned long int& idxScalarNode,
                              const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding scalar node number of a given
     * node number of the grid using the FORTRAN style.
     * \param idxNode Given node number.
     * \return Corresponding scalar node number.
     */
    unsigned long int idxNodeTuple2ScalarStyleFortran(
        const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding scalar node number of a given
     * node number of the grid using the FORTRAN style.
     * \param idxScalarNode Corresponding scalar node number (return value).
     * \param idxNode Given node number.
     */
    void idxNodeTuple2ScalarStyleFortran(
        unsigned long int& idxScalarNode,
        const ScaFES::Ntuple<int, DIM>& idxNode) const;

    /** Computes the corresponding node number of a given scalar
     * node number of the grid using the C style.
     * \param idxScalarNode Given scalar node number
     * \return Corresponding node number.
     */
    ScaFES::Ntuple<int, DIM>
    idxNodeScalar2TupleStyleC(const unsigned long int& idxScalarNode) const;

    /** Computes the corresponding node number of a given scalar
     * node number of the grid using the C style.
     * \param idxNode Corresponding node number (return value).
     * \param idxScalarNode Given scalar node number
     */
    void
    idxNodeScalar2TupleStyleC(ScaFES::Ntuple<int, DIM>& idxNode,
                              const unsigned long int& idxScalarNode) const;

    /** Computes the corresponding node number of a given scalar
     * node number of the grid using the Fortran style.
     * \param idxScalarNode Given scalar node number.
     * \return Corresponding node number.
     */
    ScaFES::Ntuple<int, DIM> idxNodeScalar2TupleStyleFortran(
        const unsigned long int& idxScalarNode) const;

    /** Computes the corresponding node number of a given scalar
     * node number of the grid using the Fortran style.
     * \param idxNode Corresponding node number (return value).
     * \param idxScalarNode Given scalar node number.
     */
    void idxNodeScalar2TupleStyleFortran(ScaFES::Ntuple<int, DIM>& idxNode,
                                         const unsigned long int& idxScalarNode)
        const;
}; // End of class. //

/*******************************************************************************
 * FREE METHODS.
 ******************************************************************************/
/** Swaps two objects of the class \c Grid. */
template <std::size_t DIM>
void swap(ScaFES::Grid<DIM>& first, ScaFES::Grid<DIM>& second);
/*----------------------------------------------------------------------------*/
/** Preprares writing an object of the class \c Grid to output. */
template <std::size_t DIM>
std::ostream& operator<<(std::ostream& output, const ScaFES::Grid<DIM>& gg);

/*******************************************************************************
 * LIFE CYCLE METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline Grid<DIM>::Grid()
: mIdxNodeFirst(0), mIdxNodeLast(1), mNnodes(2), mCoordNodeFirst(0.0),
  mCoordNodeLast(1.0), mGridsize(1.0), mIsNumberedStyleC(false)
{
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Grid<DIM>::Grid(const ScaFES::Ntuple<int, DIM>& idxNodeFirst,
                       const ScaFES::Ntuple<int, DIM>& idxNodeLast,
                       ScaFES::Ntuple<double, DIM> coordNodeFirst,
                       ScaFES::Ntuple<double, DIM> coordNodeLast)
: mIdxNodeFirst(idxNodeFirst), mIdxNodeLast(idxNodeLast),
  mNnodes(idxNodeLast - idxNodeFirst + ScaFES::Ntuple<int, DIM>(1)),
  mCoordNodeFirst(coordNodeFirst), mCoordNodeLast(coordNodeLast), mGridsize(),
  mIsNumberedStyleC(false)
{
    if (0 < this->nNodes().size())
    {
        for (std::size_t ii = 0; ii < DIM; ++ii)
        {
            this->mGridsize[ii] =
                (coordNodeLast.elem(ii) - coordNodeFirst.elem(ii)) /
                (this->nNodes().elem(ii) - 1);
        }
    }
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Grid<DIM>::Grid(const Grid<DIM>& from)
: mIdxNodeFirst(from.idxNodeFirst()), mIdxNodeLast(from.idxNodeLast()),
  mNnodes(from.nNodes()), mCoordNodeFirst(from.coordNodeFirst()),
  mCoordNodeLast(from.coordNodeLast()), mGridsize(from.gridsize()),
  mIsNumberedStyleC(from.isNumberedStyleC())
{
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM> inline Grid<DIM>& Grid<DIM>::operator=(Grid<DIM> rhs)
{
    ScaFES::swap(*this, rhs);
    return *this;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM> inline Grid<DIM>::~Grid()
{
}

/*******************************************************************************
 * GETTER METHODS.
 ******************************************************************************/
template <std::size_t DIM> inline std::size_t Grid<DIM>::dimGrid() const
{
    return DIM;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<int, DIM>& Grid<DIM>::idxNodeFirst() const
{
    return this->mIdxNodeFirst;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const int& Grid<DIM>::idxNodeFirst(const int& idx) const
{
    return this->mIdxNodeFirst.elem(idx);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<int, DIM>& Grid<DIM>::idxNodeLast() const
{
    return this->mIdxNodeLast;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const int& Grid<DIM>::idxNodeLast(const int& idx) const
{
    return this->mIdxNodeLast.elem(idx);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<int, DIM>& Grid<DIM>::nNodes() const
{
    return this->mNnodes;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const int& Grid<DIM>::nNodes(const int& idx) const
{
    return this->mNnodes.elem(idx);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM> inline int Grid<DIM>::nNodesTotal() const
{
    return this->mNnodes.size();
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<double, DIM>& Grid<DIM>::coordNodeFirst() const
{
    return this->mCoordNodeFirst;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<double, DIM>& Grid<DIM>::coordNodeLast() const
{
    return this->mCoordNodeLast;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const ScaFES::Ntuple<double, DIM>& Grid<DIM>::gridsize() const
{
    return this->mGridsize;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const double& Grid<DIM>::gridsize(const int& ii) const
{
    return this->mGridsize.elem(ii);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const bool& Grid<DIM>::isNumberedStyleC() const
{
    return this->mIsNumberedStyleC;
}

/*******************************************************************************
 * WORK METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline typename Grid<DIM>::iterator Grid<DIM>::begin() const
{
    return iterator(this, this->mIdxNodeFirst);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline typename Grid<DIM>::iterator Grid<DIM>::end() const
{
    return iterator(this, this->mIdxNodeLast);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline unsigned long int
Grid<DIM>::idxNodeTuple2Scalar(const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    unsigned long int idxScalarNode = 0L;
    this->idxNodeTuple2Scalar(idxScalarNode, idxNode);
    return idxScalarNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void
Grid<DIM>::idxNodeTuple2Scalar(unsigned long int& idxScalarNode,
                               const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    if (this->mIsNumberedStyleC)
    {
        this->idxNodeTuple2ScalarStyleC(idxScalarNode, idxNode);
    }
    else
    {
        this->idxNodeTuple2ScalarStyleFortran(idxScalarNode, idxNode);
    }
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline ScaFES::Ntuple<int, DIM>
Grid<DIM>::idxNodeScalar2Tuple(const unsigned long int& idxScalarNode) const
{
    ScaFES::Ntuple<int, DIM> idxNode;
    this->idxNodeScalar2Tuple(idxNode, idxScalarNode);
    return idxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void
Grid<DIM>::idxNodeScalar2Tuple(ScaFES::Ntuple<int, DIM>& idxNode,
                               const unsigned long int& idxScalarNode) const
{
    if (this->mIsNumberedStyleC)
    {
        this->idxNodeScalar2TupleStyleC(idxNode, idxScalarNode);
    }
    else
    {
        this->idxNodeScalar2TupleStyleFortran(idxNode, idxScalarNode);
    }
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline ScaFES::Ntuple<double, DIM>
Grid<DIM>::coordinates(const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    ScaFES::Ntuple<double, DIM> coord(0.0);

    for (std::size_t ii = 0; ii < DIM; ++ii)
    {
        coord[ii] = this->coordNodeFirst().elem(ii) +
                    this->gridsize(ii) *
                        (idxNode.elem(ii) - this->idxNodeFirst().elem(ii));
    }

    return coord;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM> inline bool Grid<DIM>::valid() const
{
    return (this->mIdxNodeFirst <= this->mIdxNodeLast);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline bool Grid<DIM>::inside(const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    return ((this->mIdxNodeFirst <= idxNode) &&
            (idxNode <= this->mIdxNodeLast));
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Grid<DIM> Grid<DIM>::extend(const int& dir, const int& nLayers)
{
    ScaFES::Ntuple<int, DIM> idxNodeFirst(this->mIdxNodeFirst);
    ScaFES::Ntuple<int, DIM> idxNodeLast(this->mIdxNodeLast);
    int idx = dir / 2;

    if (0 == (dir % 2))
    {
        idxNodeLast[idx] += nLayers;
    }
    else
    {
        idxNodeFirst[idx] -= nLayers;
    }

    return Grid<DIM>(idxNodeFirst, idxNodeLast, this->coordNodeFirst(),
                     this->coordNodeLast());
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Grid<DIM> Grid<DIM>::unionWith(const Grid<DIM>& rhs) const
{
    ScaFES::Ntuple<double, DIM> coordNodeFirstNew;
    ScaFES::Ntuple<double, DIM> coordNodeLastNew;
    ScaFES::Ntuple<int, DIM> idxNodeFirstNew =
        ScaFES::min(idxNodeFirst(), rhs.idxNodeFirst());
    ScaFES::Ntuple<int, DIM> idxNodeLastNew =
        ScaFES::max(idxNodeLast(), rhs.idxNodeLast());
    if (idxNodeFirstNew == this->idxNodeFirst())
    {
        coordNodeFirstNew = this->coordNodeFirst();
    }
    else
    {
        coordNodeFirstNew = rhs.coordNodeFirst();
    }
    if (idxNodeLastNew == this->idxNodeLast())
    {
        coordNodeLastNew = this->coordNodeLast();
    }
    else
    {
        coordNodeLastNew = rhs.coordNodeLast();
    }
    return ScaFES::Grid<DIM>(idxNodeFirstNew, idxNodeLastNew, coordNodeFirstNew,
                             coordNodeLastNew);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Grid<DIM> Grid<DIM>::intersectWith(const Grid<DIM>& rhs) const
{
    ScaFES::Ntuple<double, DIM> coordNodeFirstNew;
    ScaFES::Ntuple<double, DIM> coordNodeLastNew;
    ScaFES::Ntuple<int, DIM> idxNodeFirstNew =
        ScaFES::max(this->idxNodeFirst(), rhs.idxNodeFirst());
    ScaFES::Ntuple<int, DIM> idxNodeLastNew =
        ScaFES::min(this->idxNodeLast(), rhs.idxNodeLast());
    if (idxNodeFirstNew == this->idxNodeFirst())
    {
        coordNodeFirstNew = this->coordNodeFirst();
    }
    else
    {
        coordNodeFirstNew = rhs.coordNodeFirst();
    }
    if (idxNodeLastNew == this->idxNodeLast())
    {
        coordNodeLastNew = this->coordNodeLast();
    }
    else
    {
        coordNodeLastNew = rhs.coordNodeLast();
    }
    return ScaFES::Grid<DIM>(idxNodeFirstNew, idxNodeLastNew, coordNodeFirstNew,
                             coordNodeLastNew);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline bool Grid<DIM>::operator==(const Grid<DIM>& rhs) const
{
    return (this->idxNodeFirst() == rhs.idxNodeFirst() &&
            this->idxNodeLast() == rhs.idxNodeLast());
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline bool Grid<DIM>::operator!=(const Grid<DIM>& rhs) const
{
    return !(*this == rhs);
}
/*----------------------------------------------------------------------------*/
#ifdef SCAFES_HAVE_BOOST_SERIALIZATION
template <std::size_t DIM>
template <class Archive>
inline void Grid<DIM>::serialize(Archive& ar, const unsigned int version)
{
    if (1 <= version)
    {
        ar&(this->mIdxNodeFirst);
        ar&(this->mIdxNodeLast);
        ar&(this->mNnodes);
        ar&(this->mCoordNodeFirst);
        ar&(this->mCoordNodeLast);
        ar&(this->mGridsize);
        ar&(this->mIsNumberedStyleC);
    }
}
#endif
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline Ntuple<unsigned long int, 2 * DIM>
Grid<DIM>::connect(const ScaFES::Ntuple<int, DIM>& idxNode)
{
    // First, set up connectivity for ALL nodes (also for the boundary nodes).
    // Then, correct the connectivity entries at the boundary nodes.
    unsigned long int gap;
    unsigned long int idxScalarNode;
    ScaFES::Ntuple<unsigned long int, 2 * DIM> neighIdxNode;
    idxScalarNode = this->idxNodeTuple2Scalar(idxNode);
    gap = 1;
    for (std::size_t kk = 0; kk < DIM; ++kk)
    {
        neighIdxNode[2 * kk] = idxScalarNode - gap;
        neighIdxNode[2 * kk + 1] = idxScalarNode + gap;
        // Adapt boundary of hypercube.
        if (idxNode.elem(kk) == this->idxNodeFirst(kk))
        {
            neighIdxNode[2 * kk] = -(2 * kk + 1);
        }
        else if (idxNode.elem(kk) == this->idxNodeLast(kk))
        {
            neighIdxNode[2 * kk + 1] = -(2 * kk + 2);
        }
        gap *= this->nNodes(kk);
    }
    return neighIdxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline ScaFES::Ntuple<int, DIM>
Grid<DIM>::neigh(const ScaFES::Ntuple<int, DIM>& idxNode, const int& dir) const
{
    // Adapt boundary of hypercube.
    ScaFES::Ntuple<int, DIM> neighIdxNode(idxNode);
    int idx = dir / 2;
    if (0 == (dir % 2))
    {
        if (idxNode.elem(idx) == this->idxNodeFirst(idx))
        {
            for (std::size_t ii = 0; ii < DIM; ++ii)
            {
                neighIdxNode[ii] = -(dir + 1);
            }
        }
        else
        {
            --(neighIdxNode[idx]);
        }
    }
    else
    {
        if (idxNode.elem(idx) == this->idxNodeLast(idx))
        {
            for (std::size_t ii = 0; ii < DIM; ++ii)
            {
                neighIdxNode[ii] = -(dir + 1);
            }
        }
        else
        {
            ++(neighIdxNode[idx]);
        }
    }
    return neighIdxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline unsigned long int Grid<DIM>::idxNodeTuple2ScalarStyleC(
    const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    unsigned long int idxScalarNode = 0L;
    this->idxNodeTuple2ScalarStyleC(idxScalarNode, idxNode);
    return idxScalarNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void Grid<DIM>::idxNodeTuple2ScalarStyleC(
    unsigned long int& idxScalarNode,
    const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    idxScalarNode = idxNode.elem(0) - this->idxNodeFirst(0);

    for (std::size_t ii = 1; ii < DIM; ++ii)
    {
        idxScalarNode = idxScalarNode * this->nNodes(ii) + idxNode.elem(ii) -
                        this->idxNodeFirst(ii);
    }
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline unsigned long int Grid<DIM>::idxNodeTuple2ScalarStyleFortran(
    const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    unsigned long int idxScalarNode = 0L;
    this->idxNodeTuple2ScalarStyleFortran(idxScalarNode, idxNode);
    return idxScalarNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void Grid<DIM>::idxNodeTuple2ScalarStyleFortran(
    unsigned long int& idxScalarNode,
    const ScaFES::Ntuple<int, DIM>& idxNode) const
{
    idxScalarNode = idxNode.elem(DIM - 1) - this->idxNodeFirst(DIM - 1);
    const int dimLocal = DIM;

    for (int ii = dimLocal - 2; ii >= 0; --ii)
    {
        idxScalarNode = idxScalarNode * this->nNodes(ii) + idxNode.elem(ii) -
                        this->idxNodeFirst(ii);
    }
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline ScaFES::Ntuple<int, DIM> Grid<DIM>::idxNodeScalar2TupleStyleC(
    const unsigned long int& idxScalarNode) const
{
    ScaFES::Ntuple<int, DIM> idxNode;
    this->idxNodeScalar2TupleStyleC(idxNode, idxScalarNode);
    return idxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void Grid<DIM>::idxNodeScalar2TupleStyleC(
    ScaFES::Ntuple<int, DIM>& idxNode,
    const unsigned long int& idxScalarNode) const
{
    int tmp = idxScalarNode;
    for (std::size_t ii = DIM - 1; ii > 0; --ii)
    {
        idxNode[ii] = (tmp % this->nNodes(ii)) + this->idxNodeFirst(ii);
        tmp /= (this->nNodes(ii));
    }
    idxNode[0] = tmp;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline ScaFES::Ntuple<int, DIM> Grid<DIM>::idxNodeScalar2TupleStyleFortran(
    const unsigned long int& idxScalarNode) const
{
    ScaFES::Ntuple<int, DIM> idxNode;
    this->idxNodeScalar2TupleStyleFortran(idxNode, idxScalarNode);
    return idxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline void Grid<DIM>::idxNodeScalar2TupleStyleFortran(
    ScaFES::Ntuple<int, DIM>& idxNode,
    const unsigned long int& idxScalarNode) const
{
    int tmp = idxScalarNode;
    for (std::size_t ii = 0; ii < DIM; ++ii)
    {
        idxNode[ii] = (tmp % this->nNodes(ii)) + this->idxNodeFirst(ii);
        tmp /= (this->nNodes(ii));
    }
}

/*******************************************************************************
 * INLINED OPERATORS (FREE FUNCTIONS)
 ******************************************************************************/
template <std::size_t DIM> inline void swap(Grid<DIM>& first, Grid<DIM>& second)
{
    ScaFES::swap(first.mIdxNodeFirst, second.mIdxNodeFirst);
    ScaFES::swap(first.mIdxNodeLast, second.mIdxNodeLast);
    ScaFES::swap(first.mNnodes, second.mNnodes);
    ScaFES::swap(first.mCoordNodeFirst, second.mCoordNodeFirst);
    ScaFES::swap(first.mCoordNodeLast, second.mCoordNodeLast);
    ScaFES::swap(first.mGridsize, second.mGridsize);
    std::swap(first.mIsNumberedStyleC, second.mIsNumberedStyleC);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline std::ostream& operator<<(std::ostream& output, const Grid<DIM>& gg)
{
    output << "* GRID:" << std::endl << "  -Node First:" << gg.idxNodeFirst()
           << std::endl << "  -Node Last: " << gg.idxNodeLast() << std::endl
           << "  -Coordinates(Node First): " << gg.coordNodeFirst() << std::endl
           << "  -Coordinates(Node Last):  " << gg.coordNodeLast() << std::endl;
    return output;
}

/*******************************************************************************
 * INTERNAL CLASS ITERATOR: LIFE CYCLE METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline Grid<DIM>::Iterator::Iterator(const Grid<DIM>* gridIter,
                                     const ScaFES::Ntuple<int, DIM>& idxNode)
: mGridIter(gridIter), mIdxNode(idxNode),
  mIdxScalarNode(gridIter->idxNodeTuple2Scalar(idxNode))
{
    // TODO: Check if \c idxNode is inside underlying grid.
}

/*******************************************************************************
 * INTERNAL CLASS ITERATOR: GETTER METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline const ScaFES::Ntuple<int, DIM>& Grid<DIM>::Iterator::idxNode() const
{
    return this->mIdxNode;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline const unsigned long int& Grid<DIM>::Iterator::idxScalarNode() const
{
    return this->mIdxScalarNode;
}

/*******************************************************************************
 * INTERNAL CLASS ITERATOR: COMPARISON METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline bool Grid<DIM>::Iterator::operator!=(const Iterator& other) const
{
    return (*this < other) || (other < *this);
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline bool Grid<DIM>::Iterator::operator<(const Iterator& it) const
{
    return (this->idxScalarNode() <= it.idxScalarNode());
}

/*******************************************************************************
 * INTERNAL CLASS ITERATOR: WORK METHODS.
 ******************************************************************************/
template <std::size_t DIM>
inline const ScaFES::Ntuple<int, DIM>& Grid<DIM>::Iterator::operator*()
{
    return this->idxNode();
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline typename Grid<DIM>::Iterator& Grid<DIM>::Iterator::operator++()
{
    if (this->mGridIter->isNumberedStyleC())
    {
        return this->nextStyleC();
    }

    return this->nextStyleFortran();
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline typename Grid<DIM>::Iterator Grid<DIM>::Iterator::operator++(int /*incr*/)
{
    Iterator tmp = *this;
    ++(*this);
    return tmp;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline typename Grid<DIM>::Iterator& Grid<DIM>::Iterator::nextStyleC()
{
    ++(this->mIdxScalarNode);
    ++(this->mIdxNode[DIM - 1]);
    const int DIM_MINUS_ONE = DIM - 1;

    for (int ii = DIM_MINUS_ONE; ii > 0; --ii)
    {
        if (this->mIdxNode.elem(ii) > this->mGridIter->idxNodeLast(ii))
        {
            this->mIdxNode[ii] = this->mGridIter->idxNodeFirst(ii);
            ++(this->mIdxNode[ii - 1]);
        }
    }
    return *this;
}
/*----------------------------------------------------------------------------*/
template <std::size_t DIM>
inline typename Grid<DIM>::Iterator& Grid<DIM>::Iterator::nextStyleFortran()
{
    ++(this->mIdxScalarNode);
    ++(this->mIdxNode[0]);
    const int DIM_MINUS_ONE = DIM - 1;

    for (int ii = 0; ii < DIM_MINUS_ONE; ++ii)
    {
        if (this->mIdxNode.elem(ii) > this->mGridIter->idxNodeLast(ii))
        {
            this->mIdxNode[ii] = this->mGridIter->idxNodeFirst(ii);
            ++(this->mIdxNode[ii + 1]);
        }
    }
    return *this;
}

} // End of namespace. //

/*******************************************************************************
 ******************************************************************************/
#ifdef SCAFES_HAVE_BOOST_SERIALIZATION
namespace boost
{
namespace serialization
{
    /** Designed to set the boost serialization version of a class template. */
    template <std::size_t DIM> struct version<ScaFES::Grid<DIM>>
    {
        /** Sets the version number for serialization. */
        BOOST_STATIC_CONSTANT(unsigned long int, value = 2);
    };
} // namespace serialization
} // namespace boost
#endif

#endif


/*******************************************************************************
 *         Copyright 2003-2012 LASMEA UMR 6602 CNRS/U.B.P
 *         Copyright 2011-2012 LRI    UMR 8623 CNRS/Univ Paris Sud XI
 *
 *          Distributed under the Boost Software License, Version 1.0.
 *                 See accompanying file LICENSE.txt or copy at
 *                     http://www.boost.org/LICENSE_1_0.txt
 ******************************************************************************/
#ifndef NT2_TOOLBOX_POLYNOM_FUNCTIONS_EXPR_CONV_HPP_INCLUDED
#define NT2_TOOLBOX_POLYNOM_FUNCTIONS_EXPR_CONV_HPP_INCLUDED

#include <nt2/toolbox/polynom/functions/conv.hpp>
#include <nt2/dsl/functions/run.hpp>
#include <boost/assert.hpp>

#include <nt2/core/container/table/table.hpp>

#include <nt2/include/functions/isvector.hpp>
#include <nt2/include/functions/numel.hpp>
#include <nt2/include/functions/zeros.hpp>
#include <nt2/include/functions/colvect.hpp>
#include <nt2/include/functions/zeros.hpp>
#include <nt2/include/functions/eye.hpp>
#include <nt2/include/functions/mtimes.hpp>
#include <nt2/include/functions/isempty.hpp>
#include <nt2/include/functions/of_size.hpp>
#include <nt2/include/functions/vertcat.hpp>
#include <nt2/include/functions/extent.hpp>

namespace nt2{ namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::run_assign_, tag::cpu_
                            , (A0)(A1)(N)
                            , ((ast_<A0, nt2::container::domain>))
                              ((node_<A1, nt2::tag::conv_, N,nt2::container::domain> ))
                            )
  {
    typedef typename boost::proto::result_of::child_c<A1&,0>::type        In0;
    typedef typename boost::proto::result_of::child_c<A1&,1>::type        In1;
    typedef typename A1::value_type                                       v_t;
    typedef typename nt2::container::table<v_t>                         tab_t;
    typedef A0&                                                   result_type;
    result_type operator()(A0& out, const A1& in) const
    {
      tab_t r;
      In0 a = boost::proto::child_c<0>(in);
      In1 b = boost::proto::child_c<1>(in);
      BOOST_ASSERT_MSG(nt2::isvector(a)&&nt2::isvector(b), "a0 and a1 must be vectors.");
      size_t na = nt2::numel(a);
      size_t nb = nt2::numel(b);
      size_t dim = na+nb ? na+nb-1u : 0u;
      out.resize(of_size(1, dim));
      if (nt2::isempty(a) || nt2::isempty(b))
        out = nt2::zeros(extent(out), meta::as_<v_t>());
      else
      {
        r = nt2::eye(dim, meta::as_<v_t>());
        for(size_t i=1; i <= na; ++i)
          r(nt2::_(i, (i-1)+nb), i) = nt2::colvect(b);

        BOOST_AUTO_TPL(tmp, nt2::catv(nt2::colvect(a), nt2::zeros(nb ? nb-1u :0u, 1, meta::as_<v_t>())));
        out = nt2::mtimes(r, tmp);
        out.resize(of_size(1, numel(out)));
      }
      return out;
    }
  };
} }

#endif


#include <armadillo>

using namespace arma;

int main()
{
    vec A(100000000);
    wall_clock clock;

    clock.tic();

    for (unsigned int i = 0; i < A.size(); ++i) {
        A(i) = log(i) * log(i);
    }

    double tiempo1 = clock.toc();

    vec B(100000000);

    clock.tic();

#pragma omp parallel for
    for (unsigned int i = 0; i < A.size(); ++i) {
        B(i) = log(i) * log(i);
    }

    double tiempo2 = clock.toc();

    cout << "Tiempo bucle simple: " << tiempo1 << " segundos\n"
         << "Tiempo bucle paralelizado: " << tiempo2 << " segundos" << endl;

    return 0;
}


//  (C) Copyright Eric Niebler 2005.
//  Use, modification and distribution are subject to 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)

#include <boost/test/unit_test.hpp>
#include <boost/time_series/ordered_inserter.hpp>
#include <boost/time_series/characteristic_series.hpp>
#include <boost/time_series/constant_series.hpp>
#include <boost/time_series/delta_series.hpp>
#include <boost/time_series/dense_series.hpp>
#include <boost/time_series/heaviside_series.hpp>
#include <boost/time_series/inverse_heaviside_series.hpp>
#include <boost/time_series/piecewise_constant_series.hpp>
#include <boost/time_series/sparse_series.hpp>
#include <boost/time_series/numeric/invert_elements.hpp>
#include "./is_close.hpp"

using namespace boost;
using namespace unit_test;

///////////////////////////////////////////////////////////////////////////////
// test_invert_elements
//
void test_invert_elements()
{
    using namespace time_series;

    piecewise_constant_series<double> pwc;
    make_ordered_inserter(pwc)
        (1., 2, 4)
        (2., 6, 8)
        (4., 10, 12)
    .commit();

    piecewise_constant_series<double> expected;
    make_ordered_inserter(expected)
        (1., 2, 4)
        (.5, 6, 8)
        (.25, 10, 12)
    .commit();

    piecewise_constant_series<double> actual = invert_elements(pwc);
    BOOST_CHECK(is_close(expected, actual, 0.1));
}

///////////////////////////////////////////////////////////////////////////////
// test_invert_elements_inplace
//
void test_invert_elements_inplace()
{
    using namespace time_series;
    piecewise_constant_series<double> result;
    make_ordered_inserter(result)(.5, -inf, -10)(.25, -5, 5)(.2, 10, inf).commit();

    {
        characteristic_series<double> c(-10, 10, 2.);
        characteristic_series<double> result(-10, 10, 0.5);

        invert_elements_inplace(c);
        BOOST_CHECK(is_close(c, result, 0.1));
    }

    {
        constant_series<double> c(2.);
        constant_series<double> result(0.5);

        invert_elements_inplace(c);
        BOOST_CHECK(is_close(c, result, 0.1));
    }

    {
        delta_series<double> d(0, 2.);
        delta_series<double> result(0, 0.5);

        invert_elements_inplace(d);
        BOOST_CHECK(is_close(d, result, 0.1));
    }

    {
        dense_series<double> d;
        make_ordered_inserter(d)(2., -inf, -10)(4., -5, 5)(5., 10, inf).commit();

        invert_elements_inplace(d);
        BOOST_CHECK(is_close(d, result, 0.1));
    }

    {
        heaviside_series<double> h(10, 2.);
        heaviside_series<double> result(10, 0.5);

        invert_elements_inplace(h);
        BOOST_CHECK(is_close(h, result, 0.1));
    }

    {
        inverse_heaviside_series<double> ih(10, 2.);
        inverse_heaviside_series<double> result(10, 0.5);

        invert_elements_inplace(ih);
        BOOST_CHECK(is_close(ih, result, 0.1));
    }

    {
        piecewise_constant_series<double> pwc;
        make_ordered_inserter(pwc)(2., -inf, -10)(4., -5, 5)(5., 10, inf).commit();

        invert_elements_inplace(pwc);
        BOOST_CHECK(is_close(pwc, result, 0.1));
    }

    {
        sparse_series<double> s;
        make_ordered_inserter(s)(2., -inf, -10)(4., -5, 5)(5., 10, inf).commit();

        invert_elements_inplace(s);
        BOOST_CHECK(is_close(s, result, 0.1));
    }

}

///////////////////////////////////////////////////////////////////////////////
// init_unit_test_suite
//
test_suite* init_unit_test_suite( int argc, char* argv[] )
{
    test_suite *test = BOOST_TEST_SUITE("invert_elements test");

    test->add(BOOST_TEST_CASE(&test_invert_elements));
    test->add(BOOST_TEST_CASE(&test_invert_elements_inplace));

    return test;
}


//  Copyright Jeremy Murphy 2016.
//  Use, modification and distribution are subject to 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)

#ifdef _MSC_VER
#  pragma warning (disable : 4224)
#endif

#include "../../test/table_type.hpp"
#include "table_helper.hpp"
#include "performance.hpp"

#include <boost/math/common_factor_rt.hpp>
#include <boost/math/special_functions/prime.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/random.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
#include <functional>

using namespace std;

boost::multiprecision::cpp_int total_sum(0);

template <typename Func, class Table>
double exec_timed_test_foo(Func f, const Table& data, double min_elapsed = 0.5)
{
    double t = 0;
    unsigned repeats = 1;
    typename Table::value_type::first_type sum{0};
    stopwatch<boost::chrono::high_resolution_clock> w;
    do
    {
       for(unsigned count = 0; count < repeats; ++count)
       {
          for(typename Table::size_type n = 0; n < data.size(); ++n)
            sum += f(data[n].first, data[n].second);
       }

        t = boost::chrono::duration_cast<boost::chrono::duration<double>>(w.elapsed()).count();
        if(t < min_elapsed)
            repeats *= 2;
    }
    while(t < min_elapsed);
    total_sum += sum;
    return t / repeats;
}

template <typename T>
struct test_function_template
{
    vector<pair<T, T> > const & data;
    const char* data_name;

    test_function_template(vector<pair<T, T> > const &data, const char* name) : data(data), data_name(name) {}

    template <typename Function>
    void operator()(pair<Function, string> const &f) const
    {
        auto result = exec_timed_test_foo(f.first, data);
        report_execution_time(result,
                            string("gcd method comparison with ") + compiler_name() + string(" on ") + platform_name(),
                            string("gcd<") + data_name + string(">"),
                            string(f.second) + "\n" + boost_name());
    }
};

boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> d_0_6(0, 6);
boost::random::uniform_int_distribution<> d_1_20(1, 20);

template <class T>
T get_random_arg()
{
   int n_primes = d_0_6(rng);
   switch(n_primes)
   {
   case 0:
      // Generate a power of 2:
      return static_cast<T>(1u) << d_1_20(rng);
   case 1:
      // prime number:
      return boost::math::prime(d_1_20(rng) + 3);
   }
   T result = 1;
   for(int i = 0; i < n_primes; ++i)
      result *= boost::math::prime(d_1_20(rng) + 3) * boost::math::prime(d_1_20(rng) + 3) * boost::math::prime(d_1_20(rng) + 3) * boost::math::prime(d_1_20(rng) + 3) * boost::math::prime(d_1_20(rng) + 3);
   return result;
}

template <class T>
void test_type(const char* name)
{
   using namespace boost::math::detail;
   typedef T int_type;
   std::vector<pair<int_type, int_type> > data, data2;

   for(unsigned i = 0; i < 1000; ++i)
   {
      data.push_back(std::make_pair(get_random_arg<T>(), get_random_arg<T>()));
   }
   // uncomment and change data to data2 below to test one specific value:
   //data2.assign(data.size(), data[1]);

   //pair<int_type, int_type> test_data{ 1836311903, 2971215073 }; // 46th and 47th Fibonacci numbers. 47th is prime.

   typedef pair< function<int_type(int_type, int_type)>, string> f_test;
   array<f_test, 2> test_functions{ { { gcd_binary<int_type>, "gcd_binary" } ,{ gcd_euclidean<int_type>, "gcd_euclidean" } } };
   for_each(begin(test_functions), end(test_functions), test_function_template<int_type>(data, name));
}

int main()
{
    test_type<unsigned short>("unsigned short");
    test_type<unsigned>("unsigned");
    test_type<unsigned long>("unsigned long");
    test_type<unsigned long long>("unsigned long long");

    test_type<boost::multiprecision::uint256_t>("boost::multiprecision::uint256_t");
    test_type<boost::multiprecision::uint512_t>("boost::multiprecision::uint512_t");
    test_type<boost::multiprecision::uint1024_t>("boost::multiprecision::uint1024_t");
}


#ifndef BIG_INT_TYPE_HPP
#define BIG_INT_TYPE_HPP

#include <boost/multiprecision/cpp_int.hpp>

typedef boost::multiprecision::uint1024_t big_int;

#endif


// -----------------------------------------------------------------------------
// Fern © Geoneric
//
// This file is part of Geoneric Fern which is available under the terms of
// the GNU General Public License (GPL), version 2. If you do not want to
// be bound by the terms of the GPL, you may purchase a proprietary license
// from Geoneric (http://www.geoneric.eu/contact).
// -----------------------------------------------------------------------------
#define BOOST_TEST_MODULE fern algorithm algebra elementary trunc
#include <boost/test/unit_test.hpp>
#include "fern/core/data_customization_point/scalar.h"
#include "fern/algorithm/algebra/elementary/trunc.h"

namespace fa = fern::algorithm;

template<
    class Value,
    class Result>
void verify_value(
    Value const& value,
    Result const& result_we_want)
{
    fa::SequentialExecutionPolicy sequential;

    Result result_we_get;
    fa::algebra::trunc(sequential, value, result_we_get);
    BOOST_CHECK_CLOSE(result_we_get, result_we_want, 1e-6);
}

BOOST_AUTO_TEST_CASE(algorithm)
{
    verify_value<double, double>( 0.0,  0.0);
    verify_value<double, double>( 0.1,  0.0);
    verify_value<double, double>( 0.5,  0.0);
    verify_value<double, double>(-0.1,  0.0);
    verify_value<double, double>(-0.5,  0.0);
}


#ifndef BOOST_METAPARSE_GETTING_STARTED_5_2_HPP
#define BOOST_METAPARSE_GETTING_STARTED_5_2_HPP

// Automatically generated header file

// Definitions before section 5.1
#include "5_1.hpp"

// Definitions of section 5.1
#include <boost/metaparse/any.hpp>

using exp_parser7 =
 build_parser<
   sequence<
     int_token,                                /* The first <number> */
     repeated<sequence<plus_token, int_token>> /* The "+ <number>" elements */
   >
 >;

// query:
//    exp_parser7::apply<BOOST_METAPARSE_STRING("1 + 2 + 3 + 4")>::type

#endif


/*! \file freefallsolveeom.cpp
    \brief 空気抵抗のある自由落下系に対して運動方程式を解くクラスの実装

    Copyright © 2018 @dc1394 All Rights Reserved.
    This software is released under the BSD 2-Clause License.
*/
#include "freefallsolveeom.h"
#include <cmath>                                // for std::cos, std::floor, std::log10
#include <cstdio>                               // for std::fclose, std::fflush, std::fopen, std::fwrite
#include <optional>                             // for std::make_optional
#include <queue>                                // for std::queue
#include <string>                               // for std::to_string
#include <tuple>                                // for std::get, std::make_tuple
#include <utility>                              // for std::make_pair
#include <boost/assert.hpp>                     // for BOOST_ASSERT
#include <boost/math/constants/constants.hpp>   // for boost::math::constants::pi
#include <boost/math/tools/minima.hpp>          // for boost::math::tools::brent_find_minima
#include <boost/math/tools/roots.hpp>           // for boost::math::tools::bisect

namespace freefallsolveeom {
    // #region コンストラクタ・デストラクタ

    FreefallSolveEom::FreefallSolveEom(double dt, double tintervalgraphplot, double eps, double m, double r, double h0, double v0, Ode_Solver_type ode_solver_type) :
        acc_(gsl_interp_accel_alloc(), gsl_interp_accel_deleter),
        dt_(dt),
        eps_(eps),
        fp_(nullptr, std::fclose),
        h0_(h0),
        imax_(static_cast<std::int32_t>(tintervalgraphplot / dt)),
        l2divm2northlatitude45_(sqr(sqr(FreefallSolveEom::R0 + h0) * 2.0 * pi<double>() / (24.0 * 60.0 * 60.0)) * std::cos(pi<double>() * 0.25)),
        m_(m),
        ode_solver_type_(ode_solver_type),
        r_(r),
        spherevolume_(4.0 / 3.0 * boost::math::constants::pi<double>() * r * r * r),
        spline_pressure_(nullptr, gsl_spline_deleter),
        spline_temperature_(nullptr, gsl_spline_deleter),
        tintervalgraphplot_(tintervalgraphplot),
        tintervaloutputcsv_(std::nullopt),
        islargertintervaloutputcsv_(std::nullopt),
        v0_(v0),
		x_({ R0 + h0, v0 })
    {
        initialize();
    }

    FreefallSolveEom::FreefallSolveEom(double dt, double tintervalgraphplot, double tintervaloutputcsv, std::string const & csvfilename, double eps, double m, double r, double h0, double v0, Ode_Solver_type ode_solver_type) :
        acc_(gsl_interp_accel_alloc(), gsl_interp_accel_deleter),
        dt_(dt),
        eps_(eps),
        fp_(std::unique_ptr< FILE, decltype(&std::fclose) >(std::fopen(csvfilename.c_str(), "w"), std::fclose)),
        h0_(h0),
        imax_(static_cast<std::int32_t>(tintervalgraphplot / dt)),
        l2divm2northlatitude45_(sqr(sqr(FreefallSolveEom::R0 + h0) * 2.0 * pi<double>() / (24.0 * 60.0 * 60.0)) * std::cos(pi<double>() * 0.25)),
        m_(m),
        ode_solver_type_(ode_solver_type),
        outputtocsvdigits_(std::to_string(tintervaloutputcsv > 0.1 ? 1 : static_cast<std::int32_t>(std::ceil(-std::log10(tintervaloutputcsv))))),
        r_(r),
        spherevolume_(4.0 / 3.0 * boost::math::constants::pi<double>() * r * r * r),
        spline_pressure_(nullptr, gsl_spline_deleter),
        spline_temperature_(nullptr, gsl_spline_deleter),
        tintervalgraphplot_(tintervalgraphplot),
        tintervaloutputcsv_(std::make_optional(tintervaloutputcsv)),
        islargertintervaloutputcsv_(std::make_optional(tintervalgraphplot <= tintervaloutputcsv)),
        v0_(v0),
        x_({ R0 + h0, v0 })
    {
        initialize();
    }

    // #endregion コンストラクタ・デストラクタ

    // #region publicメンバ関数

    std::tuple< double, double, double, FreefallSolveEom::hmaxtype, FreefallSolveEom::vmaxtype, FreefallSolveEom::tandvtype, FreefallSolveEom::tandvandbooltype > FreefallSolveEom::operator()()
    {
        FreefallSolveEom::state_type result{};
        switch (ode_solver_type_) {
        case Ode_Solver_type::ADAMS_BASHFORTH_MOULTON:
            result = solveeom_run(adams_bashforth_moulton< 2, FreefallSolveEom::state_type >());
            break;

        case Ode_Solver_type::BULIRSCH_STOER:
            result = solveeom_run(bulirsch_stoer< FreefallSolveEom::state_type >(eps_, eps_));
            break;

        case Ode_Solver_type::CONTROLLED_RUNGE_KUTTA:
            result = solveeom_run(make_controlled(eps_, eps_, error_stepper_type()));
            break;

        default:
            BOOST_ASSERT(!"Ode_Solver_typeがあり得ない値になっている！");
            break;
        }

        auto const t = iscalculationfinished_ ? tend_ : t_;
        auto const h = result[0] - FreefallSolveEom::R0;
        auto const v = result[1];

        FreefallSolveEom::hmaxtype stateofhmax = std::nullopt;
        if (hmaxoftandh_)
        {
            stateofhmax = hmaxoftandh_;
        }
        else if (v0_ <= 0.0)
        {
            stateofhmax = std::make_optional(std::make_pair(0.0, h0_));
        }

        FreefallSolveEom::vmaxtype stateofvmax = std::nullopt;
        if (vmaxoftandhandv_ && std::fabs(std::get<2>(*vmaxoftandhandv_)) >= std::fabs(v0_))
        {
            stateofvmax = vmaxoftandhandv_;
        }
        else if (vmaxoftandhandv_ || (iscalculationfinished_ && std::fabs(v) < std::fabs(v0_)))
        {
            stateofvmax = std::make_optional(std::make_tuple(0.0, h0_, v0_));
        }
        else if (iscalculationfinished_ && std::fabs(v) >= std::fabs(v0_))
        {
            stateofvmax = std::make_optional(std::make_tuple(t, h, v));
        }

        return std::make_tuple(t, h, v, stateofhmax, stateofvmax, stateescapeofkarmanline_, stateescapeofexosphere_);
    }

    // #endregion publicメンバ関数

    // #region privateメンバ関数

    double FreefallSolveEom::get_P(double r) const
    {
        // ジオポテンシャル高度を取得
        auto const H = getGeopotentialFromAltitude(r);

        // 「標準大気 ー 各高度における空気の温度・圧力・密度・音速・粘性係数・動粘性係数の計算式」
        // https://pigeon-poppo.com/standard-atmosphere/ より
        if (H <= 11000.0)
        {
            return 101325.0 * std::pow(288.15 / FreefallSolveEom::get_T(r), -5.256);
        }
        else if (H <= 20000.0)
        {
            return 22632.064 * std::exp(-0.1577 * (FreefallSolveEom::MTOKM * H - 11.0));
        }
        else if (H <= 32000.0)
        {
            return 5474.889 * std::pow(216.65 / FreefallSolveEom::get_T(r), 34.163);
        }
        else if (H <= 47000.0)
        {
            return 868.019 * std::pow(228.65 / FreefallSolveEom::get_T(r), 12.201);
        }
        else if (H <= 51000.0)
        {
            return 110.906 * std::exp(-0.1262 * (FreefallSolveEom::MTOKM * H - 47.0));
        }
        else if (H <= 71000.0)
        {
            return 66.939 * std::pow(270.65 / FreefallSolveEom::get_T(r), -12.201);
        }
        else if (H <= 84852.0)
        {
            return 3.956 * std::pow(214.65 / FreefallSolveEom::get_T(r), -17.082);
        }
        else if (r - FreefallSolveEom::R0 <= 1000000.0)
        {
            return gsl_spline_eval(spline_pressure_.get(), r - FreefallSolveEom::R0, acc_.get());
        }
        else
        {
            return 0.0;
        }
    }

    double FreefallSolveEom::get_T(double r) const
    {
        // ジオポテンシャル高度を取得
        auto const H = getGeopotentialFromAltitude(r);

        // 「標準大気 ー 各高度における空気の温度・圧力・密度・音速・粘性係数・動粘性係数の計算式」
        // https://pigeon-poppo.com/standard-atmosphere/ より
        if (H <= 11000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE + 15.0 - 6.5 * FreefallSolveEom::MTOKM * H;
        }
        else if (H <= 20000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE - 56.5;
        }
        else if (H <= 32000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE - 76.5 + FreefallSolveEom::MTOKM * H;
        }
        else if (H <= 47000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE - 134.1 + 2.8 * FreefallSolveEom::MTOKM * H;
        }
        else if (H <= 51000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE - 2.5;
        }
        else if (H <= 71000.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE + 140.3 - 2.8 * FreefallSolveEom::MTOKM * H;
        }
        else if (H <= 84852.0)
        {
            return FreefallSolveEom::CELSIUSTOABSOLUTETEMPERATURE + 83.5 - 2.0 * FreefallSolveEom::MTOKM * H;
        }
        else if (r - FreefallSolveEom::R0 <= 1000000.0)
        {
            return gsl_spline_eval(spline_temperature_.get(), r - FreefallSolveEom::R0, acc_.get());
        }
        else
        {
            return 1000.0;
        }
    }

    void FreefallSolveEom::initialize()
    {
        // 高度80km以上での気圧と温度のデータ
        // 国立天文台編『理科年表』丸善出版（2012）p.331より引用
        p_data_ = { 1.0524,    0.37338,   0.18359,   0.15381,   3.2011E-2, 7.1042E-3,
                    2.5382E-3, 1.2505E-3, 7.2028E-4, 3.0395E-4, 1.5271E-4, 8.4736E-5,
                    2.4767E-5, 8.7704E-6, 3.4498E-6, 1.4518E-6, 6.4468E-7, 3.0236E-7,
                    1.5137E-7, 8.2130E-8, 4.8865E-8, 3.1908E-8, 2.2599E-8, 1.7036E-8,
                    1.3415E-8, 1.0873E-8, 7.5138E-9 };

        t_data_ = { 198.639, 186.87, 186.87, 186.87, 195.08, 240.00, 360.00, 469.27,
                    559.63,  696.29, 790.07, 854.56, 941.33, 976.01, 990.06, 995.83,
                    998.22,  999.24, 999.67, 999.85, 999.93, 999.97, 999.99, 999.99,
                    1000.0,  1000.0, 1000.0 };

        z_mesh_ = { 80000.0,  86000.0,  90000.0,  91000.0,  100000.0, 110000.0,
                    120000.0, 130000.0, 140000.0, 160000.0, 180000.0, 200000.0,
                    250000.0, 300000.0, 350000.0, 400000.0, 450000.0, 500000.0,
                    550000.0, 600000.0, 650000.0, 700000.0, 750000.0, 800000.0,
                    850000.0, 900000.0, 1000000.0 };

        BOOST_ASSERT(p_data_.size() == z_mesh_.size());
        BOOST_ASSERT(t_data_.size() == z_mesh_.size());

        spline_pressure_.reset(gsl_spline_alloc(gsl_interp_cspline, z_mesh_.size()));
        spline_temperature_.reset(gsl_spline_alloc(gsl_interp_cspline, z_mesh_.size()));

        gsl_spline_init(spline_pressure_.get(), z_mesh_.data(), p_data_.data(), z_mesh_.size());
        gsl_spline_init(spline_temperature_.get(), z_mesh_.data(), t_data_.data(), z_mesh_.size());
    }

    template <typename Stepper>
    FreefallSolveEom::state_type FreefallSolveEom::solveeom_run(Stepper const & stepper)
    {
        using namespace boost::math::tools;

        if (isfirststep_)
        {
            if (fp_)
            {
                outputresulttocsv(0.0, x_);
            }

            // 0秒目ですでにカーマン・ラインを突破しているかどうか
            if (x_[0] >= FreefallSolveEom::KARMANLINE)
            {
                stateescapeofkarmanline_ = std::make_optional(std::make_pair(0.0, v0_));
            }

            // 0秒目ですでに外気圏を脱出しているかどうか
            if (x_[0] >= FreefallSolveEom::ALTITUDEOFEXOSPHERE)
            {
                // 外気圏を脱出した際に速度が第二宇宙速度以上だったかどうか
                if (x_[1] >= FreefallSolveEom::SECONDESCAPEVELOCITYOFEXOSPHERE)
                {
                    stateescapeofexosphere_ = std::make_optional(std::make_tuple(0.0, v0_, true));

                    // 計算打ち切り
                    iscalculationfinished_ = true;
                    tend_ = 0.0;
                }
                else
                {
                    stateescapeofexosphere_ = std::make_optional(std::make_tuple(0.0, v0_, false));
                }
            }

            isfirststep_ = false;
            return x_;
        }

        std::queue<FreefallSolveEom::state_type> history{};

        for (auto i = 1; i <= imax_; i++) {
            auto const ttmp = static_cast<double>(i) * dt_;

            if (history.size() < 2)
            {
                history.push(x_);
            }
            else
            {
                history.pop();
                history.push(x_);
            }

            integrate_eom(stepper, dt_, x_);

            if (vmaxoftandhandv_ && x_[1] < 0.0)
            {
                BOOST_ASSERT(std::get<2>(*vmaxoftandhandv_) < x_[1]);
            }

            auto const statebefore = history.back();

            // カーマン・ラインを突破した際の時間を探索
            if (!stateescapeofkarmanline_ && x_[0] >= FreefallSolveEom::KARMANLINE)
            {
                auto maxit = MAXITER;
                auto res = bisect(
                    [&stepper, &statebefore, this](double t)
                {
                    auto x = statebefore;
                    integrate_eom(stepper, t, x);
                    return x[0] - FreefallSolveEom::KARMANLINE;
                },
                    0.0,
                    dt_,
                    eps_tolerance<double>(FreefallSolveEom::DIGITS),
                    maxit);

                auto const tescapeofkarmanline = (res.first + res.second) * 0.5;
                auto xtmp(statebefore);
                integrate_eom(stepper, tescapeofkarmanline, xtmp);

                stateescapeofkarmanline_ = std::make_optional(std::make_pair(t_ + static_cast<double>(i - 1) * dt_ + tescapeofkarmanline, xtmp[1]));
            }

            // 外気圏を脱出した際の時間を探索
            if (!stateescapeofexosphere_ && x_[0] >= FreefallSolveEom::ALTITUDEOFEXOSPHERE)
            {
                auto maxit = MAXITER;
                auto res = bisect(
                    [&stepper, &statebefore, this](double t)
                {
                    auto x = statebefore;
                    integrate_eom(stepper, t, x);
                    return x[0] - FreefallSolveEom::ALTITUDEOFEXOSPHERE;
                },
                    0.0,
                    dt_,
                    eps_tolerance<double>(FreefallSolveEom::DIGITS),
                    maxit);

                auto const tescapeofexosphere = (res.first + res.second) * 0.5;
                auto xtmp(statebefore);
                integrate_eom(stepper, tescapeofexosphere, xtmp);

                // 外気圏を脱出した際に速度が第二宇宙速度以上だったかどうか
                if (xtmp[1] >= FreefallSolveEom::SECONDESCAPEVELOCITYOFEXOSPHERE)
                {
                    stateescapeofexosphere_ = std::make_optional(std::make_tuple(t_ + static_cast<double>(i - 1) * dt_ + tescapeofexosphere, xtmp[1], true));

                    // 計算打ち切り
                    iscalculationfinished_ = true;
                    tend_ = t_ + static_cast<double>(i - 1) * dt_ + tescapeofexosphere;

                    return xtmp;
                }
                else
                {
                    stateescapeofexosphere_ = std::make_optional(std::make_tuple(t_ + static_cast<double>(i - 1) * dt_ + tescapeofexosphere, xtmp[1], false));
                }
            }

            // 地面に衝突する時の速度とその際の時間を探索
            if (x_[0] < FreefallSolveEom::R0)
            {
                auto maxit = MAXITER;
                auto res = bisect(
                    [&stepper, &statebefore, this](double t)
                {
                    auto x = statebefore;
                    integrate_eom(stepper, t, x);
                    return x[0] - FreefallSolveEom::R0;
                },
                    0.0,
                    dt_,
                    eps_tolerance<double>(FreefallSolveEom::DIGITS),
                    maxit);

                auto const tendtmp = (res.first + res.second) * 0.5;
                auto xtmp(statebefore);
                integrate_eom(stepper, tendtmp, xtmp);

                tend_ = t_ + static_cast<double>(i - 1) * dt_ + tendtmp;

                if (fp_)
                {
                    outputresulttocsv(tend_, xtmp);
                    std::fflush(fp_.get());
                    fp_.reset();
                }

                iscalculationfinished_ = true;

                return xtmp;
            }

            // 最高到達高度とその際の時間の探索
            if (statebefore[1] * x_[1] <= 0.0)
            {
                auto maxit = MAXITER;
                auto const res = bisect(
                    [&stepper, &statebefore, this](double t)
                {
                    auto x = statebefore;
                    integrate_eom(stepper, t, x);
                    return x[1];
                },
                    0.0,
                    dt_,
                    eps_tolerance<double>(FreefallSolveEom::DIGITS),
                    maxit);

                auto const thmaxtmp = (res.first + res.second) * 0.5;
                auto xtmp(statebefore);
                integrate_eom(stepper, thmaxtmp, xtmp);

                hmaxoftandh_ = std::make_optional(std::make_pair(t_ + static_cast<double>(i - 1) * dt_ + thmaxtmp, xtmp[0] - FreefallSolveEom::R0));
            }

            // 最高速度とその際の時間と高度の探索
            if (!vmaxoftandhandv_ && x_[1] < 0.0 && x_[1] > statebefore[1])
            {
                auto maxit = MAXITER;
                auto state2before = history.front();
                auto const res = brent_find_minima(
                    [&stepper, &state2before, this](double t)
                {
                    auto x = state2before;
                    integrate_eom(stepper, t, x);
                    return x[1];
                },
                    0.0,
                    2.0 * dt_,
                    FreefallSolveEom::DIGITS,
                    maxit);

                auto xtmp(state2before);
                integrate_eom(stepper, res.first, xtmp);

                BOOST_ASSERT(xtmp[1] < state2before[1]);
                BOOST_ASSERT(xtmp[1] < statebefore[1]);
                BOOST_ASSERT(xtmp[1] < x_[1]);

                vmaxoftandhandv_ = std::make_optional(std::make_tuple(t_ + static_cast<double>(i - 2) * dt_ + res.first, xtmp[0] - FreefallSolveEom::R0, xtmp[1]));
            }

            if (tintervaloutputcsv_ &&
                !(*islargertintervaloutputcsv_) &&
                (std::fabs(ttmp / *tintervaloutputcsv_ - std::floor(ttmp / *tintervaloutputcsv_)) <= FreefallSolveEom::ZERODECISIONTOCSV || std::fabs(ttmp / *tintervaloutputcsv_ - std::ceil(ttmp / *tintervaloutputcsv_)) <= FreefallSolveEom::ZERODECISIONTOCSV))
            {
                outputresulttocsv(t_ + ttmp, x_);
            }
        }

        cnt_++;
        t_ = static_cast<double>(cnt_) * tintervalgraphplot_;

        if (tintervaloutputcsv_ &&
            *islargertintervaloutputcsv_ &&
            (std::fabs(t_ / *tintervaloutputcsv_ - std::floor(t_ / *tintervaloutputcsv_)) <= FreefallSolveEom::ZERODECISIONTOCSV || std::fabs(t_ / *tintervaloutputcsv_ - std::ceil(t_ / *tintervaloutputcsv_)) <= FreefallSolveEom::ZERODECISIONTOCSV))
        {
            outputresulttocsv(t_, x_);
        }

        return x_;
    }

    // #endregion privateメンバ関数

    // #region templateメンバ関数の実体化

    template FreefallSolveEom::state_type FreefallSolveEom::solveeom_run<adams_bashforth_moulton< 2, FreefallSolveEom::state_type > >(adams_bashforth_moulton< 2, state_type > const & stepper);
    template FreefallSolveEom::state_type FreefallSolveEom::solveeom_run<bulirsch_stoer < FreefallSolveEom::state_type > >(bulirsch_stoer < state_type > const & stepper);
    template FreefallSolveEom::state_type FreefallSolveEom::solveeom_run<FreefallSolveEom::error_stepper_type>(error_stepper_type const & stepper);

    // #endregion templateメンバ関数の実体化

    // #region staticメンバ変数

    const double FreefallSolveEom::SECONDESCAPEVELOCITYOFEXOSPHERE = std::sqrt(2.0 * FreefallSolveEom::G * FreefallSolveEom::M / FreefallSolveEom::ALTITUDEOFEXOSPHERE);

    // #endregion staticメンバ変数
}