/* ----------------------------------------------------------------------- *//**
 *
 * @file student.hpp
 *
 * @brief Evaluate the Student's t-distribution function.
 * @author Florian Schoppmann
 * @date   November 2010
 *
 *//* -------------------------------------------------------------------- *//**
 *
 * @file student.hpp
 *
 * Emprirical results indicate that the numerical quality of the series
 * expansion from [1] (see notes below) is vastly superior to using continued
 * fractions for computing the cdf via the incomplete beta function.
 *
 * @literature
 *
 * [1] Abramowitz and Stegun, Handbook of Mathematical Functions with Formulas,
 *     Graphs, and Mathematical Tables, 1972
 *     page 948: http://people.math.sfu.ca/~cbm/aands/page_948.htm
 *
 * Further reading (for computing the Student-T cdf via the incomplete beta
 * function):
 *
 * [2] NIST Digital Library of Mathematical Functions, Ch. 8,
 *     Incomplete Gamma and Related Functions,
 *     http://dlmf.nist.gov/8.17
 *
 * [3] Lentz, Generating Bessel functions in Mie scattering calculations using
 *     continued fractions, Applied Optics, Vol. 15, No. 3, 1976
 *
 * [4] Thompson and Barnett, Coulomb and Bessel Functions of Complex Arguments
 *     and Order, Journal of Computational Physics, Vol. 64, 1986
 *
 * [5] Cuyt et al., Handbook of Continued Fractions for Special Functions,
 *     Springer, 2008
 *
 * [6] Gil et al., Numerical Methods for Special Functions, SIAM, 2008
 *
 * [7] Press et al., Numerical Recipes in C++, 3rd edition,
 *     Cambridge Univ. Press, 2007
 *
 * [8] DiDonato, Morris, Jr., Algorithm 708: Significant Digit Computation of
 *     the Incomplete Beta Function Ratios, ACM Transactions on Mathematical
 *     Software, Vol. 18, No. 3, 1992
 *
 * Approximating the Student-T distribution function with the normal
 * distribution:
 *
 * [9]  Gleason, A note on a proposed student t approximation, Computational
 *      Statistics & Data Analysis, Vol. 34, No. 1, 2000
 *
 * [10] Gaver and Kafadar, A Retrievable Recipe for Inverse t, The American
 *      Statistician, Vol. 38, No. 4, 1984
 */

/**
 * @brief Student-t cumulative distribution function
 */
DECLARE_UDF(prob, students_t_cdf)
DECLARE_UDF(prob, students_t_pdf)
DECLARE_UDF(prob, students_t_quantile)

#ifndef MADLIB_MODULES_PROB_STUDENT_T_HPP
#define MADLIB_MODULES_PROB_STUDENT_T_HPP

#include <boost/math/distributions/detail/common_error_handling.hpp>
#include <boost/math/distributions/normal.hpp>
#include <boost/math/distributions/students_t.hpp>

namespace madlib {

namespace modules {

namespace prob {

typedef boost::math::students_t_distribution<double, boost_mathkit_policy>
    students_t;

namespace {

/**
 * @brief Compute one-sided Student's t cumulative distribution function
 *
 * We use the series expansions 26.7.3 and 26.7.4 from [1] and
 * substitute sin(theta) = t/sqrt(n * z), where z = 1 + t^2/nu.
 *
 * This gives:
 * @verbatim
 *                          t
 *   A(t|1)  = 2 arctan( -------- ) ,
 *                       sqrt(nu)
 *
 *                                                    (nu-3)/2
 *             2   [            t              t         --    2 * 4 * ... * (2i)  ]
 *   A(t|nu) = - * [ arctan( -------- ) + ------------ * \  ---------------------- ]
 *             π   [         sqrt(nu)     sqrt(nu) * z   /_ 3 * ... * (2i+1) * z^i ]
 *                                                       i=0
 *           for odd nu > 1, and
 *
 *                         (nu-2)/2
 *                  t         -- 1 * 3 * ... * (2i - 1)
 *   A(t|nu) = ------------ * \  ------------------------ for even nu,
 *             sqrt(nu * z)   /_ 2 * 4 * ... * (2i) * z^i
 *                            i=0
 *
 * where A(t|nu) = Pr[|T| <= t].
 * @endverbatim
 *
 * @param t
 * @param nu Degree of freedom \f$ \nu > 0 \f$
 * @return \f$ \Pr[|T| < t] \f$ where \f$ t \geq 0 \f$, \f$ T \f$ is a Student's
 *     T-distributed random variable with \f$ \nu \f$ degrees of
 *     freedom.
 *
 * Note: The running time of calculating the series is proportional to nu.
 * We therefore use the normal distribution as an approximation for large nu.
 * Another idea for handling this case can be found in reference [8].
 */
template <class RealType>
inline
RealType
oneSidedStudentsT_CDF(const RealType& t,  uint64_t nu) {
    RealType    z,
                t_by_sqrt_nu;
    RealType    A, /* contains A(t|nu) */
                prod = 1.,
                sum = 1.;

    /* Handle main case (nu \in {1, ..., 200}) in the rest of the function. */
    z = 1. + t * t / static_cast<double>(nu);
    t_by_sqrt_nu = std::fabs(t) / std::sqrt(static_cast<double>(nu));

    if (nu == 1)
    {
        A = 2. / M_PI * std::atan(t_by_sqrt_nu);
    }
    else if (nu & 1) /* odd nu > 1 */
    {
        for (uint64_t j = 2; j + 3 <= nu; j += 2)
        {
            prod = prod * static_cast<double>(j)
                 / (static_cast<double>(j + 1) * z);
            sum = sum + prod;
        }
        A = 2 / M_PI * ( std::atan(t_by_sqrt_nu) + t_by_sqrt_nu / z * sum );
    }
    else /* even nu */
    {
        for (uint64_t j = 2; j + 2 <= nu; j += 2)
        {
            prod = prod * static_cast<double>(j - 1)
                 / (static_cast<double>(j) * z);
            sum = sum + prod;
        }
        A = t_by_sqrt_nu / std::sqrt(z) * sum;
    }

    /* A should obviously be within the interval [0,1] plus minus (hopefully
     * small) rounding errors. */
    if (A > 1.)
        A = 1.;
    else if (A < 0.)
        A = 0.;

    return A;
}

/**
 * @brief Compute parameter for normal CDF for approximating the Student's T CDF
 *
 * Gleason suggested a formula for approximating the Student's
 * t-distribution [9], which goes back to an approximation suggested in [10].
 *
 * Compared to the series expansion, this approximation satisfies
 * rel_error < 0.0001 || abs_error < 0.00000001
 * for all nu >= 200. (Tested on Mac OS X 10.6, gcc-4.2.)
 *
 * @param t
 * @param nu Degree of freedom \f$ \nu > 0 \f$
 * @returns A value \f$ z \f$ such that for a Student's t-distributed
 *     random variable \f$ T \f$ with \f$ nu \f$ degrees of freedom and a
 *     standard normally distributed random variable \f$ Z \f$, it holds that
 *     \f$ \Pr[T \leq t] \approx \Pr[Z \leq z] \f$.
 */
template <class RealType>
inline
RealType
GleasonsNormalApproxForStudentsT(const RealType& t, const RealType& nu) {
    double  g = (nu - 1.5) / ((nu - 1) * (nu - 1)),
            z = std::sqrt( std::log(1. + t * t / nu) / g );

    if (t < 0)
        z *= -1.;

    return z;
}

} // anonymous namespace

/**
 * @brief Compute Student's cumulative distribution function
 *
 * For nu >= 1000000, we just use the normal distribution as an approximation.
 * For 1000000 >= nu >= 200, we use a simple approximation from [9].
 * If nu is not within 0.01 of a natural number, we will call the student-t
 * CDF from boost. Otherwise, our approach should be much more precise than
 * using the incomplete beta function as boost does (see the references).
 *
 * We are much more cautious than usual here (it is folklore that the normal
 * distribution is a "good" estimate for Student-T if nu >= 30), but we can
 * afford the extra work as this function is not designed to be called from
 * inner loops. Performance should still be reasonably good, with at most ~100
 * iterations in any case (just one if nu >= 200).
 *
 * For nu < 200, we use the series expansions 26.7.3 and 26.7.4 from [1] and
 * substitute sin(theta) = t/sqrt(n * z), where z = 1 + t^2/nu (using
 * oneSidedStudentsT_CDF()).
 *
 * @param dist A Student's t-distribution object, containing the degree of
 *     freedom \f$ \nu \f$
 * @param t
 * @return \f$ \Pr[T < t] \f$ where \f$ t \geq 0 \f$, \f$ T \f$ is a Student's
 *     T-distributed random variable with \f$ \nu \f$ degrees of
 *     freedom.
 */
template <class RealType, class Policy>
inline
RealType
cdf(const boost::math::students_t_distribution<RealType, Policy>& dist,
    const RealType& t) {

    RealType df = dist.degrees_of_freedom();

    // FIXME: Add some justification/do some tests.
    if (!std::isfinite(df) || std::fabs(df - std::floor(df))/df > 0.01)
        return boost::math::cdf(dist, t);

    static const char* function = "madlib::modules::prob::cdf("
        "const students_t_distribution<%1%>&, %1%)";

    RealType result;
    if (!boost::math::detail::check_df(function, df, &result, Policy()))
        return result;

    if (df >= 200)
        return
            boost::math::cdf(
                boost::math::normal_distribution<RealType, Policy>(),
                df >= 1000000
                    ? t
                    : GleasonsNormalApproxForStudentsT(t, df)
            );

    // We first compute A = Pr[|T| < t]
    RealType A = oneSidedStudentsT_CDF(t, static_cast<uint64_t>(df));

    /* The Student-T distribution is obviously symmetric around t=0... */
    if (t < 0)
        /* FIXME: If A is approximately 1, we will face a loss of significance.
         *  */
        return .5 * (1. - A);
    else
        /* While we only know A in [0,1] here, the end result will be in
         * [0.5, 1]. Hence, there is no problem with adding 1 and A, even if
         * A << 1. */
        return .5 * (1. + A);
}

/**
 * @brief Compute the complement of Student's cumulative distribution function
 */
template <class RealType, class Policy>
inline
RealType
cdf(
    const boost::math::complemented2_type<
        boost::math::students_t_distribution<RealType, Policy>,
        RealType
    >& c
) {
    RealType df = c.dist.degrees_of_freedom();
    if (df >= 200) {
        static const char* function = "madlib::modules::prob::cdf("
            "const complement(students_t_distribution<%1%>&), %1%)";

        RealType result;
        if (!boost::math::detail::check_df(function, df, &result, Policy()))
            return result;

        return
            boost::math::cdf(complement(
                boost::math::normal_distribution<RealType, Policy>(),
                df >= 1000000
                    ? c.param
                    : GleasonsNormalApproxForStudentsT(c.param, df)
            ));
    }

    return prob::cdf(c.dist, -c.param);
}

template <class RealType, class Policy>
inline
RealType
pdf(const boost::math::students_t_distribution<RealType, Policy>& dist,
    const RealType& t) {
    return boost::math::pdf(dist, t);
}

template <class RealType, class Policy>
inline
RealType
pdf(
    const boost::math::complemented2_type<
        boost::math::students_t_distribution<RealType, Policy>,
        RealType
    >& c
) {
    return boost::math::pdf(c);
}

template <class RealType, class Policy>
inline
RealType
quantile(const boost::math::students_t_distribution<RealType, Policy>& dist,
    const RealType& p) {

    using namespace boost::math;

    static const char* function = "madlib::modules::prob::quantile("
        "const students_t_distribution<%1%>&, %1%)";

    // FIXME: Boost bug 6937 prevent proper argument validation.
    // https://svn.boost.org/trac/boost/ticket/6937
    // Until this is fixed upstream, we do the following checks here.
    RealType df = dist.degrees_of_freedom();
    RealType result;
    if (!detail::check_df(function, df, &result, Policy())
        || !detail::check_probability(function, p, &result, Policy()))
        return result;

    return boost::math::quantile(dist, p);
}

template <class RealType, class Policy>
inline
RealType
quantile(
    const boost::math::complemented2_type<
        boost::math::students_t_distribution<RealType, Policy>,
        RealType
    >& c
) {
    return boost::math::quantile(c);
}

} // namespace prob

} // namespace modules

} // namespace madlib

#endif // defined(MADLIB_MODULES_PROB_STUDENT_T_HPP)


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

// Copyright (c) 2007-2012 Barend Gehrels, 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)

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_AREA_HUILLER_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_AREA_HUILLER_HPP

#include <boost/geometry/strategies/spherical/distance_haversine.hpp>

#include <boost/geometry/core/radian_access.hpp>
#include <boost/geometry/util/math.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace area
{

/*!
\brief Area calculation by spherical excess / Huiller's formula
\ingroup strategies
\tparam PointOfSegment point type of segments of rings/polygons
\tparam CalculationType \tparam_calculation
\author Barend Gehrels. Adapted from:
- http://www.soe.ucsc.edu/~pang/160/f98/Gems/GemsIV/sph_poly.c
- http://williams.best.vwh.net/avform.htm
\note The version in Gems didn't account for polygons crossing the 180 meridian.
\note This version works for convex and non-convex polygons, for 180 meridian
crossing polygons and for polygons with holes. However, some cases (especially
180 meridian cases) must still be checked.
\note The version which sums angles, which is often seen, doesn't handle non-convex
polygons correctly.
\note The version which sums longitudes, see
http://trs-new.jpl.nasa.gov/dspace/bitstream/2014/40409/1/07-03.pdf, is simple
and works well in most cases but not in 180 meridian crossing cases. This probably
could be solved.

\note This version is made for spherical equatorial coordinate systems

\qbk{

[heading Example]
[area_with_strategy]
[area_with_strategy_output]

[heading See also]
[link geometry.reference.algorithms.area.area_2_with_strategy area (with strategy)]
}

*/
template
<
    typename PointOfSegment,
    typename CalculationType = void
>
class huiller
{
typedef typename boost::mpl::if_c
    <
        boost::is_void<CalculationType>::type::value,
        typename select_most_precise
            <
                typename coordinate_type<PointOfSegment>::type,
                double
            >::type,
        CalculationType
    >::type calculation_type;

protected :
    struct excess_sum
    {
        calculation_type sum;

        // Distances are calculated on unit sphere here
        strategy::distance::haversine<PointOfSegment, PointOfSegment>
                distance_over_unit_sphere;

        inline excess_sum()
            : sum(0)
            , distance_over_unit_sphere(1)
        {}
        inline calculation_type area(calculation_type radius) const
        {
            return - sum * radius * radius;
        }
    };

public :
    typedef calculation_type return_type;
    typedef PointOfSegment segment_point_type;
    typedef excess_sum state_type;

    inline huiller(calculation_type radius = 1.0)
        : m_radius(radius)
    {}

    inline void apply(PointOfSegment const& p1,
                PointOfSegment const& p2,
                excess_sum& state) const
    {
        if (! geometry::math::equals(get<0>(p1), get<0>(p2)))
        {
            calculation_type const half = 0.5;
            calculation_type const two = 2.0;
            calculation_type const four = 4.0;
            calculation_type const two_pi = two * geometry::math::pi<calculation_type>();
            calculation_type const half_pi = half * geometry::math::pi<calculation_type>();

            // Distance p1 p2
            calculation_type a = state.distance_over_unit_sphere.apply(p1, p2);

            // Sides on unit sphere to south pole
            calculation_type b = half_pi - geometry::get_as_radian<1>(p2);
            calculation_type c = half_pi - geometry::get_as_radian<1>(p1);

            // Semi parameter
            calculation_type s = half * (a + b + c);

            // E: spherical excess, using l'Huiller's formula
            // [tg(e / 4)]2   =   tg[s / 2]  tg[(s-a) / 2]  tg[(s-b) / 2]  tg[(s-c) / 2]
            calculation_type E = four * atan(sqrt(geometry::math::abs(tan(s / two)
                    * tan((s - a) / two)
                    * tan((s - b) / two)
                    * tan((s - c) / two))));

            E = geometry::math::abs(E);

            // In right direction: positive, add area. In left direction: negative, subtract area.
            // Longitude comparisons are not so obvious. If one is negative, other is positive,
            // we have to take the dateline into account.
            // TODO: check this / enhance this, should be more robust. See also the "grow" for ll
            // TODO: use minmax or "smaller"/"compare" strategy for this
            calculation_type lon1 = geometry::get_as_radian<0>(p1) < 0
                ? geometry::get_as_radian<0>(p1) + two_pi
                : geometry::get_as_radian<0>(p1);

            calculation_type lon2 = geometry::get_as_radian<0>(p2) < 0
                ? geometry::get_as_radian<0>(p2) + two_pi
                : geometry::get_as_radian<0>(p2);

            if (lon2 < lon1)
            {
                E = -E;
            }

            state.sum += E;
        }
    }

    inline return_type result(excess_sum const& state) const
    {
        return state.area(m_radius);
    }

private :
    /// Radius of the sphere
    calculation_type m_radius;
};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace services
{

template <typename Point>
struct default_strategy<spherical_equatorial_tag, Point>
{
    typedef strategy::area::huiller<Point> type;
};

// Note: spherical polar coordinate system requires "get_as_radian_equatorial"
/***template <typename Point>
struct default_strategy<spherical_polar_tag, Point>
{
    typedef strategy::area::huiller<Point> type;
};***/

} // namespace services

#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}} // namespace strategy::area

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_AREA_HUILLER_HPP


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_FNMA_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_FNMA_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!

    @ingroup group-arithmetic
    This function object computes the negated (fused) multiply add of
    its three parameters.

    @par Header <boost/simd/function/fnma.hpp>

    @par Notes
    The call `fnma(x, y, z)` is similar to `-x*y-z`

    But really conformant fused multiply/add also implies

    - only one rounding

    - no "intermediate" overflow

    fnma provides this for all integral types (however, using it on unsigned types
    is not recommanded for obvious reasons)  and also each time it is reasonable
    in terms of performance for floating ones (i.e. if the system has the hard
    wired capability).

    If you need pedantic fnma capabilities in all circumstances in your own
    code you can use the pedantic_ decorator (can be very expensive).

    @par Decorators
    - pedantic_ ensures the fnma properties and allows SIMD acceleration if available.

    @see fms, fma, fnms

    @par Example:

      @snippet fnma.cpp fnma

    @par Possible output:

      @snippet fnma.txt fnma

  **/
  Value fnma(Value const& x, Value const& y, Value const& z);
} }
#endif

#include <boost/simd/function/scalar/fnma.hpp>
#include <boost/simd/function/simd/fnma.hpp>

#endif


//
// Created by Hamza El-Kebir on 4/17/21.
//

#ifndef LODESTAR_ZEROORDERHOLD_HPP
#define LODESTAR_ZEROORDERHOLD_HPP

#include <Eigen/Dense>
#include <unsupported/Eigen/MatrixFunctions>
#include "Lodestar/systems/StateSpace.hpp"
#include "Lodestar/aux/CompileTimeQualifiers.hpp"

namespace ls {
    namespace analysis {
        /**
         * @brief Routines for computing zero-order hold transformation on state
         * space systems.
         *
         * @sa theory.ldstr.dev/discretization
         */
        class ZeroOrderHold {
        public:
            template<typename TScalar = double, int TStateDim = Eigen::Dynamic, int TInputDim = Eigen::Dynamic, int TOutputDim = Eigen::Dynamic>
            struct mallocStruct {
                template<int TTStateDim = TStateDim, int TTInputDim = TInputDim, int TTOutputDim = TOutputDim, typename ::std::enable_if<
                        (TTStateDim >= 0) && (TTInputDim >= 0) && (TTOutputDim >= 0)>::type * = nullptr>
                mallocStruct() : upperXM(decltype(upperXM)::Zero()), lowerXM(decltype(lowerXM)::Zero()),
                                 XM(decltype(XM)::Zero()), XXM(
                                decltype(XXM)::Zero())
                {}

                template<int TTStateDim = TStateDim, int TTInputDim = TInputDim, int TTOutputDim = TOutputDim, typename ::std::enable_if<
                        (TTStateDim < 0) && (TTInputDim < 0) && (TTOutputDim < 0)>::type * = nullptr>
                mallocStruct()
                {}

                Eigen::Matrix<TScalar, TStateDim, LS_STATIC_UNLESS_DYNAMIC(TStateDim + TInputDim)> upperXM;
                Eigen::Matrix<TScalar, TInputDim, LS_STATIC_UNLESS_DYNAMIC(TStateDim + TInputDim)> lowerXM;
                Eigen::Matrix<TScalar, LS_STATIC_UNLESS_DYNAMIC(TStateDim + TInputDim), LS_STATIC_UNLESS_DYNAMIC(
                        TStateDim + TInputDim)> XM;
                Eigen::Matrix<TScalar, TStateDim, LS_STATIC_UNLESS_DYNAMIC(TStateDim + TInputDim)> XXM;
            };

            /**
             * @brief Generates zero-order hold discretization from a
             * continuous-time state space system.
             *
             * @param A State matrix.
             * @param B Input matrix.
             * @param C Output matrix.
             * @param D Feedforward matrix.
             * @param dt Sampling period.
             *
             * @return Zero-order hold discrete-time state space system.
             */
            static systems::StateSpace<>
            c2d(const Eigen::MatrixXd &A, const Eigen::MatrixXd &B,
                const Eigen::MatrixXd &C, const Eigen::MatrixXd &D, double dt);

            /**
             * @brief Generates zero-order hold discretization from a
             * continuous-time state space system.
             *
             * @param A State matrix.
             * @param B Input matrix.
             * @param C Output matrix.
             * @param D Feedforward matrix.
             * @param dt Sampling period.
             *
             * @return Zero-order hold discrete-time state space system.
             */
            static systems::StateSpace<>
            c2d(Eigen::MatrixXd *A, Eigen::MatrixXd *B, Eigen::MatrixXd *C, Eigen::MatrixXd *D, double dt);

            template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
            static void c2d(const systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss, double dt,
                            systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                            mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                            LS_IS_DYNAMIC_DEFAULT(TStateDim, TInputDim, TOutputDim));

            template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
            static void c2d(const systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss, double dt,
                            systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                            mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                            LS_IS_STATIC_DEFAULT(TStateDim, TInputDim, TOutputDim));

            /**
             * @brief Generates zero-order hold discretization from a
             * continuous-time state space system.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Zero-order hold discrete-time state space system.
             */
            static systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic>
            c2d(const systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic> &ss, double dt);

            /**
             * @brief Generates zero-order hold discretization from a
             * continuous-time state space system.
             *
             * @param ss Pointer to state space system.
             * @param dt Sampling period.
             *
             * @return Zero-order hold discrete-time state space system.
             */
            static systems::StateSpace<> c2d(const systems::StateSpace<> *ss, double dt);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param A State matrix.
             * @param B Input matrix.
             * @param C Output matrix.
             * @param D Feedforward matrix.
             * @param dt Sampling period.
             *
             * @return Zero-order hold continuous-time state space system.
             */
            static systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic>
            d2c(const Eigen::MatrixXd &A, const Eigen::MatrixXd &B,
                const Eigen::MatrixXd &C, const Eigen::MatrixXd &D, double dt);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param A State matrix.
             * @param B Input matrix.
             * @param C Output matrix.
             * @param D Feedforward matrix.
             * @param dt Sampling period.
             *
             * @return Zero-order hold continuous-time state space system.
             */
            static systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic>
            d2c(Eigen::MatrixXd *A, Eigen::MatrixXd *B,
                Eigen::MatrixXd *C, Eigen::MatrixXd *D, double dt);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            static systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic>
            d2c(const systems::StateSpace<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::Dynamic> &ss, double dt);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            static systems::StateSpace<>
            d2c(const systems::StateSpace<> *ss, double dt);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * This method retrieves the sampling period from the state space
             * object.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            static systems::StateSpace<>
            d2c(const systems::StateSpace<> *ss);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * This method retrieves the sampling period from the state space
             * object.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            static systems::StateSpace<>
            d2c(const systems::StateSpace<> &ss);

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
            static void d2c(const systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss, double dt,
                            systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                            mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                            LS_IS_DYNAMIC_DEFAULT(TStateDim, TInputDim, TOutputDim));

            /**
             * @brief Reverts a zero-order hold discretization on a
             * discrete-time state space system.
             *
             * @param ss State space system.
             * @param dt Sampling period.
             *
             * @return Continuous-time state space system.
             */
            template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
            static void d2c(const systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss, double dt,
                            systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                            mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                            LS_IS_STATIC_DEFAULT(TStateDim, TInputDim, TOutputDim));
        };
    }
}

template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
void ls::analysis::ZeroOrderHold::c2d(const ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss,
                                      double dt,
                                      ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                                      ls::analysis::ZeroOrderHold::mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                                      LS_IS_DYNAMIC(TStateDim, TInputDim, TOutputDim))
{
    dt = abs(dt);

    const long n = ss->stateDim();
    const long m = ss->inputDim();

    memStruct->upperXM.block(0, 0, n, n) << (ss->getA());
    memStruct->upperXM.block(0, n, n, m) << (ss->getB());

    memStruct->lowerXM.setZero();

    memStruct->XM.block(0, 0, n, n + m) << memStruct->upperXM;
    memStruct->XM.block(n, 0, m, n + m) << memStruct->lowerXM;

    memStruct->XXM = (memStruct->XM * dt).exp().block(0, 0, n, n + m);

    out->setA(memStruct->XXM.block(0, 0, n, n));
    out->setB(memStruct->XXM.block(0, n, n, m));
    out->setC(Eigen::MatrixXd::Identity(n, n));
    out->setD(Eigen::MatrixXd::Zero(n, m));
    out->setDiscreteParams(dt, true);

}

template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
void ls::analysis::ZeroOrderHold::c2d(const ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss,
                                      double dt,
                                      ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                                      ls::analysis::ZeroOrderHold::mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                                      LS_IS_STATIC(TStateDim, TInputDim, TOutputDim))
{
    dt = abs(dt);

    memStruct->upperXM.template block<TStateDim, TStateDim>(0, 0);
    memStruct->upperXM.template block<TStateDim, TStateDim>(0, 0) << (ss->getA());
    memStruct->upperXM.template block<TStateDim, TInputDim>(0, TStateDim) << (ss->getB());

    memStruct->lowerXM.setZero();

    memStruct->XM.template block<TStateDim, TStateDim + TInputDim>(0, 0) << memStruct->upperXM;
    memStruct->XM.template block<TInputDim, TStateDim + TInputDim>(TStateDim, 0) << memStruct->lowerXM;

    memStruct->XXM = (memStruct->XM * dt).exp().template block<TStateDim, TStateDim + TInputDim>(0, 0);

    out->setA(memStruct->XXM.template block<TStateDim, TStateDim>(0, 0));
    out->setB(memStruct->XXM.template block<TStateDim, TInputDim>(0, TStateDim));
    out->setDiscreteParams(dt, true);
}

template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
void ls::analysis::ZeroOrderHold::d2c(const ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss,
                                      double dt,
                                      ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                                      ls::analysis::ZeroOrderHold::mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                                      LS_IS_DYNAMIC(TStateDim, TInputDim, TOutputDim))
{
    dt = abs(dt);

    const long n = ss->stateDim();
    const long m = ss->inputDim();

    memStruct->upperXM.block(0, 0, n, n) << (ss->getA());
    memStruct->upperXM.block(0, n, n, m) << (ss->getB());

    memStruct->lowerXM.setZero();
    memStruct->lowerXM.block(0, n, m, m).setIdentity();

    memStruct->XM.block(0, 0, n, n + m) << memStruct->upperXM;
    memStruct->XM.block(n, 0, m, n + m) << memStruct->lowerXM;

    memStruct->XXM = (memStruct->XM).log().block(0, 0, n, n + m) / dt;

    out->setA(memStruct->XXM.block(0, 0, n, n));
    out->setB(memStruct->XXM.block(0, n, n, m));
}

template<typename TScalar, int TStateDim, int TInputDim, int TOutputDim>
void ls::analysis::ZeroOrderHold::d2c(const ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *ss,
                                      double dt,
                                      ls::systems::StateSpace<TScalar, TStateDim, TInputDim, TOutputDim> *out,
                                      ls::analysis::ZeroOrderHold::mallocStruct<TScalar, TStateDim, TInputDim, TOutputDim> *memStruct,
                                      LS_IS_STATIC(TStateDim, TInputDim, TOutputDim))
{
    dt = abs(dt);

    memStruct->upperXM.template block<TStateDim, TStateDim>(0, 0) << (ss->getA());
    memStruct->upperXM.template block<TStateDim, TInputDim>(0, TStateDim) << (ss->getB());

    memStruct->lowerXM.setZero();
    memStruct->lowerXM.template block<TInputDim, TInputDim>(0, TStateDim)
            << Eigen::Matrix<TScalar, TInputDim, TInputDim>::Identity();

    memStruct->XM.template block<TStateDim, TStateDim + TInputDim>(0, 0) << memStruct->upperXM;
    memStruct->XM.template block<TInputDim, TStateDim + TInputDim>(TStateDim, 0) << memStruct->lowerXM;

    memStruct->XXM = (memStruct->XM).log().template block<TStateDim, TStateDim + TInputDim>(0, 0) / dt;

    out->setA(memStruct->XXM.template block<TStateDim, TStateDim>(0, 0));
    out->setB(memStruct->XXM.template block<TStateDim, TInputDim>(0, TStateDim));
}

#endif //LODESTAR_ZEROORDERHOLD_HPP


/**
 * @file
 * @brief NPDE homework "Handling degrees of freedom (DOFs) in LehrFEM++"
 * @author Julien Gacon
 * @date March 1st, 2019
 * @copyright Developed at ETH Zurich
 */

#include "lfppdofhandling.h"

#include <Eigen/Dense>
#include <array>
#include <memory>

#include "lf/assemble/assemble.h"
#include "lf/base/base.h"
#include "lf/geometry/geometry.h"
#include "lf/mesh/mesh.h"
#include "lf/mesh/utils/utils.h"

namespace LFPPDofHandling {

/* SAM_LISTING_BEGIN_1 */
std::array<std::size_t, 3> countEntityDofs(
    const lf::assemble::DofHandler &dofhandler) {
  std::array<std::size_t, 3> entityDofs;
  //====================
  // Your code goes here

  //STEP 0: Error handling in case we have quadrilateral cell
  const lf::mesh::Entity& ent = dofhandler.Entity(0);

  // STEP 1: obtain the mesh
  std::shared_ptr<const lf::mesh::Mesh> mesh = dofhandler.Mesh();

  // STEP 2: loop over all mesh entities
  for(std::size_t codim = 0; codim <= 2; ++codim){
    // clear the array entry to 0
    entityDofs[codim] = 0;

    for(const lf::mesh::Entity* ent: mesh->Entities(codim)){
      // LF_VERIFY_MSG(ent->RefEl() == lf::base::RefEl::kQuad(), "Unsupported cell type " << ent->RefEl());
      if(ent->RefEl() == lf::base::RefEl::kQuad()){
        throw "Unsupported cell type";
      }

      entityDofs[codim] += dofhandler.NumInteriorDofs(*ent);
    }
  }

  //====================
  return entityDofs;
}
/* SAM_LISTING_END_1 */

/* SAM_LISTING_BEGIN_2 */
std::size_t countBoundaryDofs(const lf::assemble::DofHandler &dofhandler) {
  std::shared_ptr<const lf::mesh::Mesh> mesh = dofhandler.Mesh();
  // given an entity, bd\_flags(entity) == true, if the entity is on the
  // boundary
  lf::mesh::utils::AllCodimMeshDataSet<bool> bd_flags(
      lf::mesh::utils::flagEntitiesOnBoundary(mesh));
  std::size_t no_dofs_on_bd = 0;
  //====================
  // Your code goes here

  // using bd_flags to know whether to take the entity into account
  // STEP 1: add up edges at the boundary
  for(const auto* edge: mesh->Entities(1)){

      if(bd_flags(*edge)){
          no_dofs_on_bd += dofhandler.NumInteriorDofs(*edge);
      }
  }

  // STEP 2: add up nodes at the boundary
  for(const auto* node: mesh->Entities(2)){

      if(bd_flags(*node)){
          no_dofs_on_bd += dofhandler.NumInteriorDofs(*node);
      }
  }

  //====================
  return no_dofs_on_bd;
}
/* SAM_LISTING_END_2 */

// clang-format off
/* SAM_LISTING_BEGIN_3 */
double integrateLinearFEFunction(
    const lf::assemble::DofHandler& dofhandler,
    const Eigen::VectorXd& mu) {
  double I = 0;
  //====================
  // Your code goes here

  double vol = 0.;  // store the volume

  // loop over the cells
  std::shared_ptr<const lf::mesh::Mesh> mesh_ptr = dofhandler.Mesh();
  for(const auto* cell: mesh_ptr->Entities(0)){

        // STEP 0: exception handling
        // no quadrilateral cells allowed
        if(cell->RefEl() == lf::base::RefEl::kQuad()){
          throw "Unsupported cell type";
        }

        // dofhandler dimension match
        if (dofhandler.NumLocalDofs(*cell) != 3){
          throw "Dimension mismatch for dofhandler";
        }

        // STEP 2: compute the fixed value of integration using properties of Barycentric basis
        vol = lf::geometry::Volume(*(cell->Geometry())) / 3.;  // calculate the volume for the current cell

        // STEP 3: obtain the array of global indices
        auto indices = dofhandler.GlobalDofIndices(*cell);
        auto end = indices.end();

        for(auto index_ptr = indices.begin(); index_ptr < end; ++index_ptr){
            // update I - dereference the pointer to obtain global index
            I += vol * mu(*index_ptr);
        }
  }
  //====================
  return I;
}
/* SAM_LISTING_END_3 */
// clang-format on

/* SAM_LISTING_BEGIN_4 */
double integrateQuadraticFEFunction(const lf::assemble::DofHandler &dofhandler,
                                    const Eigen::VectorXd &mu) {
  double I = 0;
  //====================
  // Your code goes here

  // loop over the cells
  std::shared_ptr<const lf::mesh::Mesh> mesh_ptr = dofhandler.Mesh();
  for(const auto* cell: mesh_ptr->Entities(0)){

    // STEP 0: exception handling

    // no quadrilateral cells
    if(cell->RefEl() == lf::base::RefEl::kQuad()){
      throw "No quadrilateral cells allowed";
    }

    // dofhandler dimension match
    if(dofhandler.NumLocalDofs(*cell) != 6){
      throw "Dimension mismatch!";
    }

    // STEP 2: compute the fixed value of integration using properties of Barycentric basis
    double vol = lf::geometry::Volume(*(cell->Geometry())) / 3.;  // calculate the volume for the current cell

    // STEP 3: obtain the array of global indices
    auto indices = dofhandler.GlobalDofIndices(*cell);
    auto end = indices.end();

    // start indexing from 0 in C++
    for(int i = 3; i <= 5; ++i){
        I += vol * mu(indices[i]);
    }

  }
  //====================
  return I;
}
/* SAM_LISTING_END_4 */

/* SAM_LISTING_BEGIN_5 */
Eigen::VectorXd convertDOFsLinearQuadratic(
    const lf::assemble::DofHandler &dofh_Linear_FE,
    const lf::assemble::DofHandler &dofh_Quadratic_FE,
    const Eigen::VectorXd &mu) {
  if (dofh_Linear_FE.Mesh() != dofh_Quadratic_FE.Mesh()) {
    throw "Underlying meshes must be the same for both DOF handlers!";
  }
  std::shared_ptr<const lf::mesh::Mesh> mesh =
      dofh_Linear_FE.Mesh();                          // get the mesh
  Eigen::VectorXd zeta(dofh_Quadratic_FE.NumDofs());  // initialise empty zeta
  // safety guard: always set zero if you're not sure to set every entry later
  // on for us this shouldn't be a problem, but just to be sure
  zeta.setZero();

  for (const auto *cell : mesh->Entities(0)) {
    // check if the spaces are actually linear and quadratic
    if(dofh_Linear_FE.NumLocalDofs(*cell) != 3){
      throw "The dimension of linear FE space should be 3!";
    }

    if(dofh_Quadratic_FE.NumLocalDofs(*cell)!= 6){
      throw "The dimension of Quadratic FE space should be 6!";
    }
    //====================
    // Your code goes here
    // STEP 3: obtain the array of global indices
    auto linear_indices = dofh_Linear_FE.GlobalDofIndices(*cell);
    auto quadratic_indices = dofh_Quadratic_FE.GlobalDofIndices(*cell);
    //====================
    // get the global dof indices of the linear and quadratic FE spaces, note
    // that the vectors obey the LehrFEM++ numbering, which we will make use of
    // lin\_dofs will have size 3 for the 3 dofs on the nodes and
    // quad\_dofs will have size 6, the first 3 entries being the nodes and
    // the last 3 the edges
    //====================
    // Your code goes here
    // assign the coefficients of mu to the correct entries of zeta, use
    // the previous subproblem 2-9.a

    // using just one loop from 0-3 but deal with all 6 entries at the time
    for(int i = 0; i < 3; ++i){
          zeta(quadratic_indices[i]) = mu(linear_indices[i]); // the first 3 entries of basis func have coeff 1

          // make use of the pattern of the basis functions used in the linear combination
          zeta(quadratic_indices[i + 3]) = 0.5 * (mu(linear_indices[i]) + mu(linear_indices[(i + 1) % 3]));
    }
    //====================
  }
  return zeta;
}
/* SAM_LISTING_END_5 */

}  // namespace LFPPDofHandling


////////////////////////////////////////////////////////////////////////////////
//  Copyright (c)      2012 Zach Byerly
//  Copyright (c) 2011-2012 Bryce Adelstein-Lelbach
//
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
////////////////////////////////////////////////////////////////////////////////

//
// This is a program written to evolve in time the equation:
//
// D^2 U / Dt^2 = c^2  D^2 U / Dx^2
//
// The parameter alpha = c*dt/dx must be less than 1 to ensure the stability
//     of the algorithm.
// Discretizing the equation and solving for U(t+dt,x) yields
// alpha^2 * (U(t,x+dx)+U(t,x-dx))+2(1-alpha^2)*U(t,x) - U(t-dt,x)
//
// For the first timestep, we approximate U(t-dt,x) by u(t+dt,x)-2*dt*du/dt(t,x)
//

// Include statements.
#include <hpx/hpx_init.hpp>
#include <hpx/runtime/actions/plain_action.hpp>
#include <hpx/include/async.hpp>
#include <hpx/lcos/future_wait.hpp>
#include <hpx/include/iostreams.hpp>
#include <hpx/util/format.hpp>

#include <boost/math/constants/constants.hpp>

#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <mutex>
#include <vector>

using boost::program_options::variables_map;
using boost::program_options::options_description;
using boost::program_options::value;

using hpx::naming::id_type;
using hpx::naming::invalid_id;

using hpx::lcos::future;
using hpx::async;
using hpx::lcos::wait;

using hpx::util::high_resolution_timer;

using hpx::init;
using hpx::finalize;
using hpx::find_here;

using hpx::cout;
using hpx::flush;

///////////////////////////////////////////////////////////////////////////////
// Globals.

//double const alpha_squared = 0.25;
double alpha_squared = 0;

// Initialized in hpx_main.
id_type here = invalid_id;
double pi = 0.;
double c = 0.;
double dt = 0.;
double dx = 0.;

// Command line argument.
std::uint64_t nt = 0;
std::uint64_t nx = 0;

struct data{
  // Default constructor: data d1;
  data()
    : mtx()
    , u_value(0.0)
    , computed(false)
  {}

  // Copy constructor: data d1; data d2(d1);
  // We can't copy the mutex, because mutexs are noncopyable.
  data(
       data const& other
       )
    : mtx()
    , u_value(other.u_value)
    , computed(other.computed)
  {}

  data& operator=(
      data const& other
      )
  {
    u_value = other.u_value;
    computed = other.computed;
    return *this;
  }

  hpx::lcos::local::mutex mtx;
  double u_value;
  bool computed;
};

std::vector<std::vector<data> > u;

///////////////////////////////////////////////////////////////////////////////
// Forward declaration of the wave function.
double wave(std::uint64_t t, std::uint64_t x);

// Any global function needs to be wrapped into a plain_action if it should be
// invoked as a HPX-thread.
// This generates the required boilerplate we need for remote invocation.
HPX_PLAIN_ACTION(wave);

double calculate_u_tplus_x(double u_t_xplus, double u_t_x, double u_t_xminus,
                           double u_tminus_x)
{
  double u_tplus_x = alpha_squared*(u_t_xplus + u_t_xminus)
    + 2.0*(1-alpha_squared)*u_t_x - u_tminus_x;
  return u_tplus_x;
}

double calculate_u_tplus_x_1st(double u_t_xplus, double u_t_x,
                               double u_t_xminus, double u_dot)
{
  double u_tplus_x = 0.5*alpha_squared*(u_t_xplus + u_t_xminus)
    + (1-alpha_squared)*u_t_x + dt*u_dot;
  return u_tplus_x;
}

double wave(std::uint64_t t, std::uint64_t x)
{
  std::lock_guard<hpx::lcos::local::mutex> l(u[t][x].mtx);
  //  hpx::util::format_to(cout, "calling wave... t={1} x={2}\n", t, x) << flush;
  if (u[t][x].computed)
    {
      //cout << ("already computed!\n") << flush;
      return u[t][x].u_value;
    }
  u[t][x].computed = true;

  if (t == 0) //first timestep are initial values
    {
      //        hpx::util::format_to(cout, "first timestep\n") << flush;
      u[t][x].u_value = std::sin(2.*pi*x*dx); // initial u(x) value
      return u[t][x].u_value;
    }
  future<double> n1;

  // NOT using ghost zones here... just letting the stencil cross the periodic
  // boundary.
  if (x == 0)
    n1 = async<wave_action>(here,t-1,nx-1);
  else
    n1 = async<wave_action>(here,t-1,x-1);

  future<double> n2 = async<wave_action>(here,t-1,x);

  future<double> n3;

  if (x == (nx-1))
    n3 = async<wave_action>(here,t-1,0);
  else
    n3 = async<wave_action>(here,t-1,x+1);

  double u_t_xminus = n1.get(); //get the futures
  double u_t_x = n2.get();
  double u_t_xplus = n3.get();

  if (t == 1) //second time coordinate handled differently
    {
      double u_dot = 0;// initial du/dt(x)
      u[t][x].u_value = calculate_u_tplus_x_1st(u_t_xplus,u_t_x,u_t_xminus,u_dot);
      return u[t][x].u_value;
    } else {
    double u_tminus_x = async<wave_action>(here,t-2,x).get();
    u[t][x].u_value =  calculate_u_tplus_x(u_t_xplus,u_t_x,u_t_xminus,u_tminus_x);
    return u[t][x].u_value;
  }

}

///////////////////////////////////////////////////////////////////////////////
int hpx_main(variables_map& vm)
{
  here = find_here();
  pi = boost::math::constants::pi<double>();

  //    dt = vm["dt-value"].as<double>();
  //    dx = vm["dx-value"].as<double>();
  //    c = vm["c-value"].as<double>();
  nx = vm["nx-value"].as<std::uint64_t>();
  nt = vm["nt-value"].as<std::uint64_t>();

  c = 1.0;

  dt = 1.0/(nt-1);
  dx = 1.0/(nx-1);
  alpha_squared = (c*dt/dx)*(c*dt/dx);

  // check that alpha_squared satisfies the stability condition
  if (0.25 < alpha_squared)
    {
      cout << (("alpha^2 = (c*dt/dx)^2 should be less than 0.25 for stability!\n"))
          << flush;
    }

  u = std::vector<std::vector<data> >(nt, std::vector<data>(nx));

  hpx::util::format_to(cout, "dt = {1}\n", dt) << flush;
  hpx::util::format_to(cout, "dx = {1}\n", dx) << flush;
  hpx::util::format_to(cout, "alpha^2 = {1}\n", alpha_squared) << flush;

  {
    // Keep track of the time required to execute.
    high_resolution_timer t;

    std::vector<future<double> > futures;
    for (std::uint64_t i=0;i<nx;i++)
      futures.push_back(async<wave_action>(here,nt-1,i));

    // open file for output
    std::ofstream outfile;
    outfile.open ("output.dat");

    wait(futures, [&](std::size_t i, double n)
         { double x_here = i*dx;
           hpx::util::format_to(outfile, "{1} {2}\n", x_here, n) << flush; });

    outfile.close();

    char const* fmt = "elapsed time: {1} [s]\n";
    hpx::util::format_to(std::cout, fmt, t.elapsed());
  }

  finalize();
  return 0;
}

///////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
  // Configure application-specific options.
  options_description
    desc_commandline("Usage: " HPX_APPLICATION_STRING " [options]");

  desc_commandline.add_options()
    ( "dt-value"
      , value<double>()->default_value(0.05)
      , "dt parameter of the wave equation")

    ( "dx-value"
      , value<double>()->default_value(0.1)
      , "dx parameter of the wave equation")

    ( "c-value"
      , value<double>()->default_value(1.0)
      , "c parameter of the wave equation")

    ( "nx-value"
      , value<std::uint64_t>()->default_value(100)
      , "nx parameter of the wave equation")

    ( "nt-value"
      , value<std::uint64_t>()->default_value(400)
      , "nt parameter of the wave equation")
    ;

  // Initialize and run HPX.
  return init(desc_commandline, argc, argv);
}


/*
 *  Zp.hpp
 *
 *  Created by Andrea Bedini on 19/09/08.
 *  Copyright (c) 2008-2014, Andrea Bedini <andrea.bedini@gmail.com>.
 *
 *  Distributed under the terms of the Modified BSD License.
 *  The full license is in the file COPYING, distributed as part of
 *  this software.
 *
 */

#ifndef ZP_HPP
#define ZP_HPP

#include <boost/cstdint.hpp>
#include <boost/operators.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/utility/enable_if.hpp>

#include <iosfwd>

namespace modular {
  class Zp;
}

namespace boost {
  template<> struct is_integral <modular::Zp> : public true_type {};
  template<> struct is_signed   <modular::Zp> : public false_type {};
  template<> struct is_unsigned   <modular::Zp> : public true_type {};
}

namespace modular {
  using boost::enable_if;
  using boost::disable_if;

  using boost::uint64_t;

  typedef long double ldouble;

  inline uint64_t mul_mod(uint64_t a, uint64_t b, uint64_t m)
  {
    uint64_t x = a * b;
    uint64_t y = m * (uint64_t)( (ldouble)a * (ldouble)b/m + (ldouble)1/2 );
    uint64_t r = x - y;
    if ( (int64_t)r < 0 )  r += m;
    return  r;
  }

  class Zp : boost::ring_operators< Zp
	   , boost::equality_comparable< Zp
	   > >
  {
    static uint64_t M;
    uint64_t rep_;

  public:
    Zp() : rep_(0) { }
    Zp(Zp const& x) : rep_(x.rep_) { }

    template<class T>
    explicit Zp(T n, typename boost::enable_if<boost::is_signed<T> >::type* = 0)
      : rep_( n < 0 ?  M - ((- n) % M) : n % M)
    { }

    template<class T>
    explicit Zp(T n, typename boost::disable_if<boost::is_signed<T> >::type* = 0)
      : rep_(n % M)
    { }

    operator unsigned long() const { return rep_; }

    static uint64_t get_modulus() {
      return M;
    }

    static void set_modulus(uint64_t p) {
      M = p;
    }

    // addable
    Zp& operator+=(Zp const& x)
    {
      if (rep_ >= M - x.rep_)
	rep_ -= M - x.rep_;
      else
	rep_ += x.rep_;
      return *this;
    }

    // subtractable
    Zp& operator-=(Zp const& x)
    {
      if (rep_ >= x.rep_)
	rep_ -= x.rep_;
      else
	rep_ = M - x.rep_ + rep_;
      return *this;
    }

    // multipliable
    Zp& operator*=(Zp const& x)
    {
      rep_ = mul_mod(rep_, x.rep_, M);
      // rep_ *= x.rep_;
      // rep_ %= M;
      return *this;
    }

    Zp operator-() const
    {
      return Zp(M - rep_);
    }

    // equality_comparable
    bool operator==(Zp const& rhs) const
    {
      return rep_ == rhs.rep_;
    }

    friend std::ostream& operator<<(std::ostream& o, Zp const& x)
    {
      return o << x.rep_ << " (" << M << ")";
    }
  };

  uint64_t Zp::M;
}

#endif


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

#include <pmp/algorithms/SurfaceSmoothing.h>
#include <pmp/algorithms/DifferentialGeometry.h>

#include <Eigen/Dense>
#include <Eigen/Sparse>

//=============================================================================

namespace pmp {

//=============================================================================

using SparseMatrix = Eigen::SparseMatrix<double>;
using Triplet = Eigen::Triplet<double>;

//=============================================================================

void SurfaceSmoothing::explicitSmoothing(unsigned int iters,
                                         bool useUniformLaplace)
{
    auto points = m_mesh.vertexProperty<Point>("v:point");
    auto eweight = m_mesh.addEdgeProperty<Scalar>("e:cotan");
    auto laplace = m_mesh.addVertexProperty<Point>("v:laplace");

    // compute Laplace weight per edge: cotan or uniform
    if (useUniformLaplace)
    {
        for (auto e : m_mesh.edges())
            eweight[e] = 1.0;
    }
    else
    {
        for (auto e : m_mesh.edges())
            eweight[e] = std::max(0.0, cotanWeight(m_mesh, e));
    }

    // smoothing iterations
    SurfaceMesh::Vertex vv;
    SurfaceMesh::Edge e;
    for (unsigned int i = 0; i < iters; ++i)
    {
        // step 1: compute Laplace for each vertex
        for (auto v : m_mesh.vertices())
        {
            Point l(0, 0, 0);

            if (!m_mesh.isSurfaceBoundary(v))
            {
                Scalar w(0);

                for (auto h : m_mesh.halfedges(v))
                {
                    vv = m_mesh.toVertex(h);
                    e = m_mesh.edge(h);
                    l += eweight[e] * (points[vv] - points[v]);
                    w += eweight[e];
                }

                l /= w;
            }

            laplace[v] = l;
        }

        // step 2: move each vertex by its (damped) Laplacian
        for (auto v : m_mesh.vertices())
        {
            points[v] += 0.5f * laplace[v];
        }
    }

    // clean-up custom properties
    m_mesh.removeVertexProperty(laplace);
    m_mesh.removeEdgeProperty(eweight);
}

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

void SurfaceSmoothing::implicitSmoothing(Scalar timestep,
                                         bool useUniformLaplace)
{
    if (!m_mesh.nVertices())
        return;

    // properties
    auto points = m_mesh.vertexProperty<Point>("v:point");
    auto vweight = m_mesh.addVertexProperty<Scalar>("v:area");
    auto eweight = m_mesh.addEdgeProperty<Scalar>("e:cotan");
    auto idx = m_mesh.addVertexProperty<int>("v:idx", -1);

    // compute weights: cotan or uniform
    if (useUniformLaplace)
    {
        for (auto v : m_mesh.vertices())
            vweight[v] = 1.0 / m_mesh.valence(v);
        for (auto e : m_mesh.edges())
            eweight[e] = 1.0;
    }
    else
    {
        for (auto v : m_mesh.vertices())
            vweight[v] = 0.5 / voronoiArea(m_mesh, v);
        for (auto e : m_mesh.edges())
            eweight[e] = std::max(0.0, cotanWeight(m_mesh, e));
    }

    // collect free (non-boundary) vertices in array free_vertices[]
    // assign indices such that idx[ free_vertices[i] ] == i
    unsigned i = 0;
    std::vector<SurfaceMesh::Vertex> free_vertices;
    free_vertices.reserve(m_mesh.nVertices());
    for (auto v : m_mesh.vertices())
    {
        if (!m_mesh.isSurfaceBoundary(v))
        {
            idx[v] = i++;
            free_vertices.push_back(v);
        }
    }
    const unsigned int n = free_vertices.size();

    // A*X = B
    SparseMatrix A(n, n);
    Eigen::MatrixXd B(n, 3);

    // nonzero elements of A as triplets: (row, column, value)
    std::vector<Triplet> triplets;

    // setup matrix A and rhs B
    double ww;
    SurfaceMesh::Vertex v, vv;
    SurfaceMesh::Edge e;
    for (unsigned int i = 0; i < n; ++i)
    {
        v = free_vertices[i];

        // rhs row
        B(i, 0) = points[v][0] / vweight[v];
        B(i, 1) = points[v][1] / vweight[v];
        B(i, 2) = points[v][2] / vweight[v];

        // lhs row
        ww = 0.0;
        for (auto h : m_mesh.halfedges(v))
        {
            vv = m_mesh.toVertex(h);
            e = m_mesh.edge(h);
            ww += eweight[e];

            // fixed boundary vertex -> right hand side
            if (m_mesh.isSurfaceBoundary(vv))
            {
                B(i, 0) -= -timestep * eweight[e] * points[vv][0];
                B(i, 1) -= -timestep * eweight[e] * points[vv][1];
                B(i, 2) -= -timestep * eweight[e] * points[vv][2];
            }
            // free interior vertex -> matrix
            else
            {
                triplets.emplace_back(i, idx[vv], -timestep * eweight[e]);
            }
        }

        // center vertex -> matrix
        triplets.emplace_back(i, i, 1.0 / vweight[v] + timestep * ww);
    }

    // build sparse matrix from triplets
    A.setFromTriplets(triplets.begin(), triplets.end());

    // solve A*X = B
    Eigen::SimplicialLDLT<SparseMatrix> solver(A);
    Eigen::MatrixXd X = solver.solve(B);
    if (solver.info() != Eigen::Success)
    {
        std::cerr << "SurfaceSmoothing: Could not solve linear system\n";
    }
    else
    {
        // copy solution
        for (unsigned int i = 0; i < n; ++i)
        {
            v = free_vertices[i];
            points[v][0] = X(i, 0);
            points[v][1] = X(i, 1);
            points[v][2] = X(i, 2);
        }
    }

    // clean-up
    m_mesh.removeVertexProperty(idx);
    m_mesh.removeVertexProperty(vweight);
    m_mesh.removeEdgeProperty(eweight);
}

//=============================================================================
} // namespace pmp
//=============================================================================


/*************************************************************************
Copyright (c) 2019 Cognitics, Inc.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "sfa/ConvexHull.h"
#include "sfa/Point.h"
#include "sfa/Polygon.h"
#include "sfa/PointMath.h"
#include "sfa/RingMath.h"
#include <float.h>
#include <cmath>
#include <boost/foreach.hpp>
namespace sfa {

//MELKMAN ========================================================================================
    int MelkmanHull::isLeft(const Point& p, const Point& p1, const Point& p2)
    {
        double a = (p1.X() - p.X())*(p2.Y() - p.Y()) - (p2.X() - p.X())*(p1.Y() - p.Y());
        if (a < -SFA_EPSILON) return -1;
        else if (a > SFA_EPSILON) return 1;
        else return 0;
    }

    Geometry* MelkmanHull::apply(const LineString* a)
    {
        //check if the linestring is long enough
        if (a->getNumPoints() < 3)
            new LineString(a);

        std::vector<Point> points;
        for (int i = 0; i < a->getNumPoints(); i++)
        {
            points.push_back(*a->getPointN(i));
        }

        //remove any redundant points
        if (a->isClosed()) points.pop_back();

        //create the dequeue
        Point* D = new Point[2*points.size() + 1];
        int bottom = int(points.size()) - 2;
        int top = bottom + 3;

        //special case, the first three points are collinear
        //in that case keep moving down
        int first = 0;
        int second = 1;
        int third = 2;

        while(third < int(points.size()))
        {
            if (Collinear(points[first],points[second],points[third]))
            {
                second++;
                third++;
            }
            else break;
        }
        if (third >= int(points.size()))
            return new LineString(a);

        //set up the first three points so that they are in CCW order
        if (isLeft(points[first], points[second], points[third]) > 0)
        {
            D[bottom+1] = points[first];
            D[bottom+2] = points[second];
        }
        else
        {
            D[bottom+1] = points[second];
            D[bottom+2] = points[first];
        }
        D[bottom] = points[third];
        D[top] = points[third];

        for (int i = third++; i < int(points.size()); i++)
        {
            // test if next vertex is inside the deque hull
            if ((isLeft(D[bottom], D[bottom+1], points[i]) > 0) &&
                (isLeft(D[top-1], D[top], points[i]) > 0) )
                    continue;

            // Find the rightmost vertex to connect to
            while (isLeft(D[bottom], D[bottom+1], points[i]) <= 0)
                ++bottom;

            D[--bottom] = points[i];    // insert V[i] at bot of deque

            // find the leftmost vertex to connect to
            while (isLeft(D[top-1], D[top], points[i]) <= 0)
                --top;

            D[++top] = points[i];        // push V[i] onto top of deque
        }

        LineString* result = new LineString;
        for (int i = 0; i <= (top-bottom); i++)
        {
            D[bottom + i].setZ(0);
            result->addPoint(D[bottom + i]);
        }
        if (result->isEmpty())
        {
            delete result;
            return NULL;
        }
        else
        {
            Polygon* returnValue = new Polygon;
            returnValue->addRing(result);
            return returnValue;
        }
    }

//GRAHAM =========================================================================================

    void GrahamHull::findLowest(void)
    {
        int n = 0;
        for (int i = 0; i < num; i++)
        {
            if ( (P[n]->p.Y() > P[i]->p.Y()) || (P[n]->p.Y() == P[i]->p.Y() && P[n]->p.X() > P[i]->p.X()) )
                n = i;
        }

        if (n != 0) swap(0,n);
    }

    int GrahamHull::comparePoints(GrahamPoint* a, GrahamPoint* b)
    {
        int comp = CrossProduct(P[0]->p,a->p,P[0]->p,b->p);
        if (comp > 0) return -1;
        else if (comp < 0) return 1;
        else
        {
            double x = abs(a->p.X() - P[0]->p.X()) - abs(b->p.X() - P[0]->p.X());
            double y = abs(a->p.Y() - P[0]->p.Y()) - abs(b->p.Y() - P[0]->p.Y());

            if (x<0 || y<0)
            {
                a->toDelete = true;
                return -1;
            }
            else if (x>0 || y>0)
            {
                b->toDelete = true;
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

    void GrahamHull::swap(int i, int j)
    {
        GrahamPoint* temp = P[i];
        P[i] = P[j];
        P[j] = temp;
    }

    void GrahamHull::quickSort(int left, int right)
    {
        int i = left, j = right;
        GrahamPoint* pivot = P[(left + right) / 2];

        while (i <= j) {
            while (comparePoints(P[i],pivot) < 0)
                i++;
            while (comparePoints(P[j],pivot) > 0)
                j--;
            if (i <= j) {
                swap(i,j);
                i++;
                j--;
            }
        };

        if (left < j)
            quickSort(left, j);
        if (i < right)
            quickSort(i, right);
    }

    void GrahamHull::sort(void)
    {
        quickSort(1,num-1);
    }

    void GrahamHull::compress(void)
    {
    /*    This method doesn't actually delete anything, it just pushes the "deleted" points to the end of
        the vector and ignores them for the rest of the computation.
    */
        int j = 0;
        int i = 0;
        int nextGroup = 0;

        //pre compress first group to ensure that the first point is not deleted
        for (i = 1; i < num; i++)
        {
            if (P[0]->p.equals(&P[i]->p)) nextGroup++;
            else break;
        }
        j = 1;

        //compress the rest of the array
        for (i = nextGroup+1; i < num; i++)
        {
            bool deleteGroup = false;
            nextGroup = i;

            //Find the end of this duplicate group
            for (int k = i+1; k < num; k++)
            {
                if (P[k]->p.equals(&P[i]->p))
                {
                    if (P[k]->toDelete) deleteGroup = true;
                    nextGroup++;
                }
                else break;
            }

            if (!P[i]->toDelete && !deleteGroup)
            {
                P[j] = P[i];
                j++;
            }
            i = nextGroup;
        }

        num = j;
    }

    void GrahamHull::compute(void)
    {
        if (num == 0)
        {
            return;
        }
        else if (num == 1)
        {
            hull.push_back(_P[0].p);
        }
        else if (num == 2)
        {
            hull.push_back(_P[0].p);
            hull.push_back(_P[1].p);
        }
        else if (num == 3)
        {
            hull.push_back(_P[0].p);
            hull.push_back(_P[1].p);
            hull.push_back(_P[2].p);
            hull.push_back(_P[0].p);

            if (    (hull[1].X() - hull[0].X())*(hull[2].Y() - hull[0].Y()) <
                    (hull[2].X() - hull[0].X())*(hull[1].Y() - hull[0].Y()) )
                    std::reverse(hull.begin(),hull.end());
        }
        else
        {
            findLowest();
            sort();
            compress();

        /*
            Quick test to ensure there is a hull..if there are only 2 points in the queue, then that means
            the rest were deleted because of collinearity, meaning the result is simple a line between the two
            points.
        */
            if (num == 2)
            {
                hull.push_back(P[0]->p);
                hull.push_back(P[1]->p);
                hull.push_back(hull.front());
                return;
            }

        //    Create Resulting Hull
            hull.push_back(P[0]->p);
            hull.push_back(P[1]->p);
            hull.push_back(P[2]->p);

            int i = 3;

            while (i < num)
            {
                Point p1 = hull[hull.size() - 2];
                Point p2 = hull.back();
                if (CrossProduct(p1,p2,p1,P[i]->p) > 0)
                {
                    hull.push_back(P[i]->p);
                    i++;
                }
                else
                {
                    hull.pop_back();
                }

            }

        //    Close hull
            hull.push_back(hull.front());
        }

    //    cleanup
        _P.clear();
        P.clear();
        num = int(hull.size());
    }

    GrahamHull::GrahamHull(const std::vector<Point>& points)
    {
        // First snap all points to a grid using SFA_EPSILON
        // This Graham Hull algorithm falls apart when points are too
        // close together because the left/right detection fails.
        // There is a bug in this code that causes both points to be deleted
        // in some cases where two points are within SFA_EPSILON distance.
        // For now this is an effective workaround to that problem
        const double epsilon = SFA_EPSILON;
        std::set<sfa::Point> grid;//All unique points here
        BOOST_FOREACH(sfa::Point pt,points)
        {
            double closest_dist = DBL_MAX;
            BOOST_FOREACH(const sfa::Point &gridpt,grid)
            {
                closest_dist = std::min<double>(closest_dist,gridpt.distance(&pt));
            }
            // If pt is not within epsilon distance from any grid point, insert pt into the grid
            if(closest_dist>epsilon)
            {
                grid.insert(pt);
            }
        }
        std::vector<sfa::Point> snappedPoints;
        snappedPoints.insert(snappedPoints.begin(),grid.begin(),grid.end());

        num = int(snappedPoints.size());
        _P.reserve(num);
        for (int j = 0; j < num; j++)
        {
            _P.push_back(GrahamPoint(snappedPoints[j]));
            _P.back().p.setZ(0);
            P.push_back(&_P.back());
        }

        compute();
    }

    GrahamHull::GrahamHull(const MultiPoint* a)
    {
        num = a->getNumGeometries();
        _P.reserve(num);
        for (int j = 0; j < num; j++)
        {
            _P.push_back(GrahamPoint(*static_cast<Point*>(a->getGeometryN(j+1))));
            _P.back().p.setZ(0);
            P.push_back(&_P.back());
        }

        compute();
    }

    Geometry* GrahamHull::getHullGeometry(void)
    {
        if (num == 0)
            return NULL;
        else if (num == 1)
            return new Point(hull[0]);
        else
        {
            LineString* line = new LineString;
            for (std::vector<Point>::iterator it = hull.begin(), end = hull.end(); it != end; it++)
                line->addPoint(*it);
            if (num == 2)
                return line;
            else
            {
                Polygon* polygon = new Polygon;
                polygon->addRing(line);
                return polygon;
            }
        }
    }

    std::vector<Point> GrahamHull::getHullPoints(void)
    {
        return hull;
    }

    Geometry* GrahamHull::apply(const MultiPoint* a)
    {
        GrahamHull hull(a);
        return hull.getHullGeometry();
    }

    std::vector<Point> GrahamHull::apply(const std::vector<Point>& points)
    {
        GrahamHull hull(points);
        return hull.getHullPoints();
    }

}


#define EIGEN_USE_MKL_ALL
#define MLT_VERBOSE

#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <chrono>

#include <Eigen/Core>

#include "utils/optimizers/stochastic_gradient_descent.hpp"
#include "utils/loss_functions.hpp"
#include "utils/activation_functions.hpp"
#include "../misc.hpp"
#include "models/transformers/sparse_autoencoder.hpp"
#include "models/classifiers/optimizable_linear_classifier.hpp"
#include "models/classifiers/perceptron.hpp"

using namespace std;

using namespace Eigen;

using namespace mlt::models::transformers;
using namespace mlt::models::classifiers;
using namespace mlt::utils;

vector<vector<double>> parseCsv(string file, bool skipFirstLine, char delim = ',') {
	vector<vector<double>> result;

	ifstream str(file.c_str());
		string line;

	if (skipFirstLine) {
		getline(str, line);
	}

	while (getline(str, line)) {
		vector<double> currentLine;
		stringstream lineStream(line);
		string cell;

		while (getline(lineStream, cell, delim)) {
			currentLine.push_back(stod(cell));
		}

		result.push_back(currentLine);
	}

	return result;
}

tuple<MatrixXd, VectorXi> load_training_data(string file) {
	auto data = parseCsv(file, true);
	MatrixXd features(data[0].size(), data.size());
	VectorXi classes(data.size());

	for(auto i = 0; i < data.size(); i++) {
		classes(i) = data[i][0];
		for (auto j = 1; j < data[i].size(); j++) {
			features(j - 1, i) = data[i][j] / 255.0;
		}
	}

	return{ features, classes };
}

MatrixXd load_test_data(string file) {
	auto data = parseCsv(file, true);
	MatrixXd features(data[0].size(), data.size());

	for (auto i = 0; i < data.size(); i++) {
		for (auto j = 0; j < data[i].size(); j++) {
			features(j, i) = data[i][j] / 255.0;
		}
	}

	return features;
}

void output_result(const VectorXi& result, string filename) {
	ofstream outputFile(filename, ofstream::app);

	outputFile << "ImageId,Label" << endl;

	for (size_t i = 0; i < result.rows(); i++) {
		outputFile << i + 1 << "," << result(i) << endl;
	}

	outputFile.close();

}

string current_date_time() {
	auto now = chrono::system_clock::now();
	auto in_time_t = chrono::system_clock::to_time_t(now);

	stringstream ss;
	ss << put_time(localtime(&in_time_t), "%Y%m%d%H%M%S");
	return ss.str();
}

template <class Model, class TargetType>
double split_crossvalidation(Model& model, const MatrixXd& features, const TargetType& target, double training_percentage) {
	assert(features.cols() == target.cols() || (target.cols() == 1 && features.cols() == target.rows()));
	assert(training_percentage > 0 && training_percentage < 1);
	auto training = static_cast<size_t>(round(features.cols() * training_percentage));
	auto validation = features.cols() - training;
	assert(training > 0 && validation > 0);

	auto training_features = features.leftCols(training);
	auto training_target = target.cols() == 1 ? target.block(0, 0, training, 1) : target.block(0, 0, target.rows(), training);
	auto validation_features = features.rightCols(validation);
	auto validation_target = target.cols() == 1 ? target.block(training, 0, validation, 1) : target.block(0, training, target.rows(), validation);

	model.fit(training_features, training_target);

	return model.score(validation_features, validation_target);
}

template <class Transformation, class Model, class TargetType>
double split_crossvalidation(Transformation& transformation, Model& model, const MatrixXd& features, const TargetType& target, double training_percentage) {
	assert(features.cols() == target.cols() || (target.cols() == 1 && features.cols() == target.rows()));
	assert(training_percentage > 0 && training_percentage < 1);
	auto training = static_cast<size_t>(round(features.cols() * training_percentage));
	auto validation = features.cols() - training;
	assert(training > 0 && validation > 0);

	auto training_features = features.leftCols(training);
	auto training_target = target.cols() == 1 ? target.block(0, 0, training, 1) : target.block(0, 0, target.rows(), training);
	auto validation_features = features.rightCols(validation);
	auto validation_target = target.cols() == 1 ? target.block(training, 0, validation, 1) : target.block(0, training, target.rows(), validation);

	transformation.fit(training_features, training_target);
	model.fit(transformation.transform(training_features), training_target);

	return model.score(transformation.transform(validation_features), validation_target);
}

int main() {
	print_info();
	cout << endl;

	MatrixXd features;
	VectorXi classes;

	tie(features, classes) = load_training_data("train.csv");

	//auto output_filename = "digit_recognizer_cross_val_" + current_date_time() + ".csv";
	//ofstream output_file(output_filename);

	//output_file << "Type;Batch Size;Learning Rate;Weight Decay;L2 Regularization;Score" << endl;

	for (auto batch_size : { 512, 256, 1024, 2048 }) {
		for (auto learning_rate : { 0.1, 0.01, 0.001, 0.0001 }) {
			for (auto decay : { 0.99, 0.95 }) {
				for (auto regularization : { 0.005, 0.0005 }) {

					using loss_t = loss_functions::SoftmaxLoss;
					using act_t = activation_functions::SigmoidActivation;
					using opt_t = optimizers::StochasticGradientDescent<>;

					loss_t loss;
					act_t act;
					opt_t opt1(batch_size, 10, 0.001, decay);
					opt_t opt2(batch_size, 200, learning_rate, decay);

					//auto model1 = create_sparse_autoencoder(196, act, act, opt1, 3e-3, 0.1, 3);
					auto model1 = create_perceptron(200, true, learning_rate, true);
					auto model2 = OptimizableLinearClassifier<loss_t, opt_t>(loss, opt2, regularization, true);

					//auto score = split_crossvalidation(model1, model2, features, classes, 0.8);
					//cout << "Score for SparseAutoencoder -> Softmax " << batch_size << " " << learning_rate << " " << decay << " " << regularization << ": " << score << endl;
					//output_file << "SparseAutoencoder -> Softmax;" << batch_size << ";" << learning_rate << ";" << decay << ";" << regularization << ";" << score << endl;

					auto score = split_crossvalidation(model1, features, classes, 0.8);
					cout << "Score for Perceptron " << batch_size << " " << learning_rate << " " << decay << " " << regularization << ": " << score << endl;
					// output_file << "SparseAutoencoder -> Softmax;" << batch_size << ";" << learning_rate << ";" << decay << ";" << regularization << ";" << score << endl;

					auto score2 = split_crossvalidation(model2, features, classes, 0.8);
					cout << "Score for Softmax " << batch_size << " " << learning_rate << " " << decay << " " << regularization << ": " << score2 << endl;
					//output_file << "Softmax;" << batch_size << ";" << learning_rate << ";" << decay << ";" << regularization << ";" << score2 << endl;
				}
			}
		}
	}

	cin.get();

	return 0;
}


//
//  su3_x.hpp
//  Lattice
//
//  Created by Evan Owen on 10/19/18.
//  Copyright © 2018 Evan Owen. All rights reserved.
//

#ifndef su3_x_hpp
#define su3_x_hpp

#include <random>
#include <complex>
#include <Eigen/Dense>

class su3_x_lattice;
typedef Eigen::Matrix<std::complex<double>, 3, 3> su3_link;
typedef Eigen::Matrix<std::complex<double>, 1, 3> su3_vector;
#define su3_identity su3_link::Identity()
#define su3_zero su3_link::Zero()

typedef Eigen::Matrix<std::complex<double>, 2, 2> su2_link;
#define su2_identity su2_link::Identity()
#define su2_zero su2_link::Zero()

#define I complex<double>(0,1)
#define Q(s) s->link[0]
#define D_MAX 6
#define SQRT3   1.73205080756887729352744634151  // sqrt(3)
#define SQRT1_3 0.577350269189625764509148780502 // 1 / sqrt(3)

class su3_x_site {
public:
    // variables
    su3_x_lattice* lattice; // parent lattice
    su3_link link[D_MAX]; // link values in each direction
    su3_link link_inverse[D_MAX]; // link inverse values
    su3_link p_link[D_MAX]; // conjugate momenta in each direction
    su3_x_site* forward[D_MAX]; // adjacent sites in the forward direction
    su3_x_site* backward[D_MAX]; // adjacent sites in the backward direction
    std::mt19937* gen; // random number generator for this time-slice
    bool forward_edge; // site is at the forward edge of the extra dimension
    bool backward_edge; // site is at the backward edge of the extra dimension
    bool is_locked;
    double eps;
    su3_link wf_z[D_MAX]; // exponent for wilson flow

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    // methods
    void init(su3_x_lattice* lattice, su3_x_site* lattice_sites, int s);
    su3_link make_unitary(const su3_link& g);
    su3_link cayley_ham(const su3_link& Q);
    bool lock();
    void unlock();
    double rand_double(double min = 0.0, double max = 1.0);
    int rand_int(int min, int max);
    double rand_normal(double mean = 0.0, double stdev = 1.0);
    void reset_links(bool cold);
    void copy_links(su3_x_site* site);
    void read_links(std::ifstream& ckptFile, bool bigEndian);
    void set_link(int d, const su3_link& value);
    void init_momenta();
    su2_link create_su2(bool random);
    su3_link create_link(bool random);
    double action();
    double hamiltonian();
    void hmc_step_p(double frac);
    void hmc_step_link();
    su3_link p_link_dot(int d);
    double link_trace();
    double plaq();
    su3_link plaquette(int d1, int d2);
    su3_link staple(int d1);
    su3_link reverse_staple(int d1);
    su3_link staple_x(int d1);
    su3_link reverse_staple_x(int d1);
    su3_link cloverleaf(int d1, int d2);
    double wilson_loop(int a, int b);
    std::complex<double> polyakov_loop(int r);
    double correlator(int T);
    double field_strength();
//    double field_strength_x();
    double topological_charge();
    double mag_U();
    double abs_U();
    double four_point(int T, int R);
    su3_link overrelax(su3_link g);
    void relax(bool coulomb);
    double sum_landau();
    double sum_coulomb();
    su3_link sum_G(bool coulomb);
    double heat_bath();
    double heat_bath_link(int d1);
    void cool();
    void cool_link(int d1);
    void wilson_flow(su3_x_site* target, int step);
    void wilson_flow_link(su3_x_site* target, int step, int d);
    void stout_smear(su3_x_site* target, double rho);
    void stout_smear_link(su3_x_site* target, double rho, int d1);
};

class su3_x_lattice {
public:
    // variables
    int N; // array size (spacial)
    int T; // array size (time)
    int N5; // 5th dimension size
    int D; // number of dimensions
    double beta; // coupling factor
    double eps5; // ratio of coupling in extra dimension to normal coupling
    int n_sites; // number of sites in the entire lattice
    int n_sites_5; // number of sites in a 5d sub-lattice
    int n_slice; // number of sites in each time slice of the entire lattice
    int n5_center; // center lattice
    std::vector<su3_x_site> site; // site values
    std::vector<std::mt19937> gen; // array of random number generators (one for each time-slice)
    std::vector<su2_link> sigma; // pauli spin matrices
    std::vector<su3_link> lambda; // gell-mann matrices
    double z; // heat bath metropolis factor
    int verbose;
    bool parallel;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    // hmc
    std::vector<su3_x_site> site_1; // site values for hmc
    double dt; // hmc step size
    int n_steps; // hmc step count
    int hmc_accept; // number of accepted hmc configurations
    int hmc_count; // number of attempted hmc configurations

    // time step for wilson flow algorithm
    double wf_dt;

    // methods
    su3_x_lattice(int N, int T, int N5, int D, double beta, double eps5, bool cold = false);
    su3_x_lattice(su3_x_lattice* lattice);
    su3_x_lattice(int N, int T, int N5, int D, double beta, double eps5, std::ifstream& ckptFile, bool isNersc = false);
    ~su3_x_lattice();
    void init();
    double rand_double(double min = 0.0, double max = 1.0);
    int rand_int(int min, int max);
    double rand_normal(double mean = 0.0, double stdev = 1.0);
    int get_site(int s, int d, int n);
    double link_trace();
    double plaq(int n5);
    double action(int n5);
    double wilson_loop(int a, int b, int n5);
    double polyakov_loop(int R, int n5);
    double correlator(int T, int n5);
    double four_point(int T, int R, int n5);
    double hamiltonian();
    void hmc(int n_sweeps = 0, bool update_dt = false, bool no_metropolis = false);
    void heat_bath(int n_sweeps = 0);
    void cool(int n5, int n_sweeps = 0);
//    void wilson_flow(int n_sweeps = 0);
    void wilson_flow(int n5, int n_sweeps = 0);
    void stout_smear(int n5, double rho = 0.1, int n_sweeps = 0);
    double field_strength(int n5);
//    double field_strength_x(int n5);
    double topological_charge(int n5);
    int thermalize(int n_min = 0, int n_max = 0);
    double ave_link_t(int n5);
    double theta(bool coulomb, int n5);
    long double relax(long double error_target, bool coulomb, int n5);
    void write_four_point(const char* filename, bool coulomb, int n5);
    void write_correlator(const char* filename, bool coulomb, int n5);
};

#endif /* su3_x_hpp */