// Boost.Geometry (aka GGL, Generic Geometry Library)
// This file is manually converted from PROJ4

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Geometry Library by
//     Barend Gehrels (Geodan, Amsterdam)
//     Adam Wulkiewicz

// Original copyright notice:

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

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

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_ELL_SET_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_ELL_SET_HPP

#include <string>
#include <vector>

#include <boost/geometry/formulas/eccentricity_sqr.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/srs/projections/dpar.hpp>
#include <boost/geometry/srs/projections/impl/pj_datum_set.hpp>
#include <boost/geometry/srs/projections/impl/pj_ellps.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/proj4.hpp>
#include <boost/geometry/srs/projections/spar.hpp>

namespace boost { namespace geometry { namespace projections {

namespace detail {

/* set ellipsoid parameters a and es */
template <typename T>
inline T SIXTH() { return .1666666666666666667; } /* 1/6 */
template <typename T>
inline T RA4() { return .04722222222222222222; } /* 17/360 */
template <typename T>
inline T RA6() { return .02215608465608465608; } /* 67/3024 */
template <typename T>
inline T RV4() { return .06944444444444444444; } /* 5/72 */
template <typename T>
inline T RV6() { return .04243827160493827160; } /* 55/1296 */

template <typename T>
inline T pj_ell_b_to_es(T const& a, T const& b)
{
    return 1. - (b * b) / (a * a);
}

/************************************************************************/
/*                          pj_ell_init_ellps()                         */
/************************************************************************/

// Originally a part of pj_ell_set()
template <typename T>
inline bool pj_ell_init_ellps(srs::detail::proj4_parameters const& params, T &a, T &b)
{
    /* check if ellps present and temporarily append its values to pl */
    std::string name = pj_get_param_s(params, "ellps");
    if (! name.empty())
    {
        const pj_ellps_type<T>* pj_ellps = pj_get_ellps<T>().first;
        const int n = pj_get_ellps<T>().second;
        int index = -1;
        for (int i = 0; i < n && index == -1; i++)
        {
            if(pj_ellps[i].id == name)
            {
                index = i;
            }
        }

        if (index == -1) {
            BOOST_THROW_EXCEPTION( projection_exception(error_unknown_ellp_param) );
        }

        pj_ellps_type<T> const& pj_ellp = pj_ellps[index];
        a = pj_ellp.a;
        b = pj_ellp.b;

        return true;
    }

    return false;
}

template <typename T>
inline bool pj_ell_init_ellps(srs::dpar::parameters<T> const& params, T &a, T &b)
{
    /* check if ellps present and temporarily append its values to pl */
    typename srs::dpar::parameters<T>::const_iterator
        it = pj_param_find(params, srs::dpar::ellps);
    if (it != params.end())
    {
        if (it->template is_value_set<int>())
        {
            const pj_ellps_type<T>* pj_ellps = pj_get_ellps<T>().first;
            const int n = pj_get_ellps<T>().second;
            int i = it->template get_value<int>();

            if (i < 0 || i >= n) {
                BOOST_THROW_EXCEPTION( projection_exception(error_unknown_ellp_param) );
            }

            pj_ellps_type<T> const& pj_ellp = pj_ellps[i];
            a = pj_ellp.a;
            b = pj_ellp.b;
        }
        else if (it->template is_value_set<T>())
        {
            a = it->template get_value<T>();
            b = a;
        }
        else if (it->template is_value_set<srs::spheroid<T> >())
        {
            srs::spheroid<T> const& s = it->template get_value<srs::spheroid<T> >();
            a = geometry::get_radius<0>(s);
            b = geometry::get_radius<2>(s);
        }
        else
        {
            BOOST_THROW_EXCEPTION( projection_exception(error_unknown_ellp_param) );
        }

        return true;
    }

    return false;
}

template
<
    typename Params,
    int I = srs::spar::detail::tuples_find_index_if
        <
            Params,
            srs::spar::detail::is_param_tr<srs::spar::detail::ellps_traits>::pred
        >::value,
    int N = boost::tuples::length<Params>::value
>
struct pj_ell_init_ellps_static
{
    template <typename T>
    static bool apply(Params const& params, T &a, T &b)
    {
        typedef typename boost::tuples::element<I, Params>::type param_type;
        typedef srs::spar::detail::ellps_traits<param_type> traits_type;
        typedef typename traits_type::template model_type<T>::type model_type;

        param_type const& param = boost::tuples::get<I>(params);
        model_type const& model = traits_type::template model<T>(param);

        a = geometry::get_radius<0>(model);
        b = geometry::get_radius<2>(model);

        return true;
    }
};
template <typename Params, int N>
struct pj_ell_init_ellps_static<Params, N, N>
{
    template <typename T>
    static bool apply(Params const& , T & , T & )
    {
        return false;
    }
};

template <typename T, BOOST_GEOMETRY_PROJECTIONS_DETAIL_TYPENAME_PX>
inline bool pj_ell_init_ellps(srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX> const& params,
                              T &a, T &b)
{
    return pj_ell_init_ellps_static
        <
            srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX>
        >::apply(params, a, b);
}

/************************************************************************/
/*                             pj_ell_init()                            */
/************************************************************************/

/* initialize geographic shape parameters */
// This function works differently than the original pj_ell_set().
// It doesn't push parameters defined in ellps into params list.
// Instead it tries to use size (a, R) and shape (es, e, rf, f, b) parameters
// and then if needed falls back to ellps, then to datum and then to the default WGS84
template <typename Params, typename T>
inline void pj_ell_init(Params const& params, T &a, T &es)
{
    /* check for varying forms of ellipsoid input */
    a = es = 0.;

    /* R takes precedence */
    if (pj_param_f<srs::spar::r>(params, "R", srs::dpar::r, a)) {
        /* empty */
    } else { /* probable elliptical figure */

        // Set ellipsoid's size parameter
        a = pj_get_param_f<T, srs::spar::a>(params, "a", srs::dpar::a);
        bool is_a_set = a != 0.0;

        // Set ellipsoid's shape parameter
        T b = 0.0;
        bool is_ell_set = false;
        if (pj_param_f<srs::spar::es>(params, "es", srs::dpar::es, es)) {/* eccentricity squared */
            /* empty */
            is_ell_set = true;
        } else if (pj_param_f<srs::spar::e>(params, "e", srs::dpar::e, es)) { /* eccentricity */
            es = es * es;
            is_ell_set = true;
        } else if (pj_param_f<srs::spar::rf>(params, "rf", srs::dpar::rf, es)) { /* recip flattening */
            if (es == 0.0) {
                BOOST_THROW_EXCEPTION( projection_exception(error_rev_flattening_is_zero) );
            }
            es = 1./ es;
            es = es * (2. - es);
            is_ell_set = true;
        } else if (pj_param_f<srs::spar::f>(params, "f", srs::dpar::f, es)) { /* flattening */
            es = es * (2. - es);
            is_ell_set = true;
        } else if (pj_param_f<srs::spar::b>(params, "b", srs::dpar::b, b)) { /* minor axis */
            es = pj_ell_b_to_es(a, b);
            is_ell_set = true;
        } /* else es == 0. and sphere of radius a */

        // NOTE: Below when ellps is used to initialize a and es
        // b is not set because it only has sense together with a
        // but a could have been set separately before, e.g. consider passing:
        // a=1 ellps=airy (a=6377563.396 b=6356256.910)
        // after setting size parameter a and shape parameter from ellps
        // b has to be recalculated

        // If ellipsoid's parameters are not set directly
        //   use ellps parameter
        if (! is_a_set || ! is_ell_set) {
            T ellps_a = 0, ellps_b = 0;
            if (pj_ell_init_ellps(params, ellps_a, ellps_b)) {
                if (! is_a_set) {
                    a = ellps_a;
                    is_a_set = true;
                }
                if (! is_ell_set) {
                    es = pj_ell_b_to_es(ellps_a, ellps_b);
                    is_ell_set = true;
                }
            }
        }

        // If ellipsoid's parameters are not set
        //   use ellps defined by datum parameter
        if (! is_a_set || ! is_ell_set)
        {
            const pj_datums_type<T>* datum = pj_datum_find_datum<T>(params);
            if (datum != NULL)
            {
                pj_ellps_type<T> const& pj_ellp = pj_get_ellps<T>().first[datum->ellps];
                if (! is_a_set) {
                    a = pj_ellp.a;
                    is_a_set = true;
                }
                if (! is_ell_set) {
                    es = pj_ell_b_to_es(pj_ellp.a, pj_ellp.b);
                    is_ell_set = true;
                }
            }
        }

        // If ellipsoid's parameters are still not set
        //   use default WGS84
        if ((! is_a_set || ! is_ell_set)
         && ! pj_get_param_b<srs::spar::no_defs>(params, "no_defs", srs::dpar::no_defs))
        {
            pj_ellps_type<T> const& pj_ellp = pj_get_ellps<T>().first[srs::dpar::ellps_wgs84];
            if (! is_a_set) {
                a = pj_ellp.a;
                is_a_set = true;
            }
            if (! is_ell_set) {
                es = pj_ell_b_to_es(pj_ellp.a, pj_ellp.b);
                is_ell_set = true;
            }
        }

        if (b == 0.0)
            b = a * sqrt(1. - es);

        /* following options turn ellipsoid into equivalent sphere */
        if (pj_get_param_b<srs::spar::r_au>(params, "R_A", srs::dpar::r_au)) { /* sphere--area of ellipsoid */
            a *= 1. - es * (SIXTH<T>() + es * (RA4<T>() + es * RA6<T>()));
            es = 0.;
        } else if (pj_get_param_b<srs::spar::r_v>(params, "R_V", srs::dpar::r_v)) { /* sphere--vol. of ellipsoid */
            a *= 1. - es * (SIXTH<T>() + es * (RV4<T>() + es * RV6<T>()));
            es = 0.;
        } else if (pj_get_param_b<srs::spar::r_a>(params, "R_a", srs::dpar::r_a)) { /* sphere--arithmetic mean */
            a = .5 * (a + b);
            es = 0.;
        } else if (pj_get_param_b<srs::spar::r_g>(params, "R_g", srs::dpar::r_g)) { /* sphere--geometric mean */
            a = sqrt(a * b);
            es = 0.;
        } else if (pj_get_param_b<srs::spar::r_h>(params, "R_h", srs::dpar::r_h)) { /* sphere--harmonic mean */
            a = 2. * a * b / (a + b);
            es = 0.;
        } else {
            T tmp;
            bool i = pj_param_r<srs::spar::r_lat_a>(params, "R_lat_a", srs::dpar::r_lat_a, tmp);
            if (i || /* sphere--arith. */
                pj_param_r<srs::spar::r_lat_g>(params, "R_lat_g", srs::dpar::r_lat_g, tmp)) { /* or geom. mean at latitude */

                tmp = sin(tmp);
                if (geometry::math::abs(tmp) > geometry::math::half_pi<T>()) {
                    BOOST_THROW_EXCEPTION( projection_exception(error_ref_rad_larger_than_90) );
                }
                tmp = 1. - es * tmp * tmp;
                a *= i ? .5 * (1. - es + tmp) / ( tmp * sqrt(tmp)) :
                    sqrt(1. - es) / tmp;
                es = 0.;
            }
        }
    }

    /* some remaining checks */
    if (es < 0.) {
        BOOST_THROW_EXCEPTION( projection_exception(error_es_less_than_zero) );
    }
    if (a <= 0.) {
        BOOST_THROW_EXCEPTION( projection_exception(error_major_axis_not_given) );
    }
}

template <typename Params>
struct static_srs_tag_check_nonexpanded
{
    typedef typename boost::mpl::if_c
        <
            srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param_t<srs::spar::r>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::r_au>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::r_v>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::r_a>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::r_g>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::r_h>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param_t<srs::spar::r_lat_a>::pred
                >::value
         || srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param_t<srs::spar::r_lat_g>::pred
                >::value,
            srs_sphere_tag,
            // NOTE: The assumption here is that if the user defines either one of:
            // b, es, e, f, rf parameters then he wants to define spheroid, not sphere
            typename boost::mpl::if_c
                <
                    srs::spar::detail::tuples_exists_if
                        <
                            Params, srs::spar::detail::is_param_t<srs::spar::b>::pred
                        >::value
                 || srs::spar::detail::tuples_exists_if
                        <
                            Params, srs::spar::detail::is_param_t<srs::spar::es>::pred
                        >::value
                 || srs::spar::detail::tuples_exists_if
                        <
                            Params, srs::spar::detail::is_param_t<srs::spar::e>::pred
                        >::value
                 || srs::spar::detail::tuples_exists_if
                        <
                            Params, srs::spar::detail::is_param_t<srs::spar::rf>::pred
                        >::value
                 || srs::spar::detail::tuples_exists_if
                        <
                            Params, srs::spar::detail::is_param_t<srs::spar::f>::pred
                        >::value,
                    srs_spheroid_tag,
                    void
                >::type
        >::type type;
};

template <typename Params>
struct static_srs_tag_check_ellps
{
    typedef typename geometry::tag
        <
            typename srs::spar::detail::ellps_traits
                <
                    typename srs::spar::detail::tuples_find_if
                        <
                            Params,
                            srs::spar::detail::is_param_tr<srs::spar::detail::ellps_traits>::pred
                        >::type
                >::template model_type<double>::type // dummy type
        >::type type;
};

template <typename Params>
struct static_srs_tag_check_datum
{
    typedef typename geometry::tag
        <
            typename srs::spar::detail::ellps_traits
                <
                    typename srs::spar::detail::datum_traits
                        <
                            typename srs::spar::detail::tuples_find_if
                                <
                                    Params,
                                    srs::spar::detail::is_param_tr<srs::spar::detail::datum_traits>::pred
                                >::type
                        >::ellps_type
                >::template model_type<double>::type // dummy type
        >::type type;
};

template
<
    typename Params,
    typename NonExpandedTag = typename static_srs_tag_check_nonexpanded
                                <
                                    Params
                                >::type,
    typename EllpsTag = typename static_srs_tag_check_ellps
                            <
                                Params
                            >::type,
    typename DatumTag = typename static_srs_tag_check_datum
                            <
                                Params
                            >::type
>
struct static_srs_tag
{
    // User passed one of the non-ellps, non-datum parameters
    typedef NonExpandedTag type;
};

template <typename Params, typename EllpsTag, typename DatumTag>
struct static_srs_tag<Params, void, EllpsTag, DatumTag>
{
    // User didn't pass neither one of the non-ellps, non-datum parameters
    // but passed ellps
    typedef EllpsTag type;
};

template <typename Params, typename DatumTag>
struct static_srs_tag<Params, void, void, DatumTag>
{
    // User didn't pass neither one of the non-ellps, non-datum parameters
    // nor ellps parameter but passed datum parameter
    typedef DatumTag type;
};

template <typename Params>
struct static_srs_tag<Params, void, void, void>
{
    // User didn't pass any parameter defining model
    // so use default or generate error
    typedef typename boost::mpl::if_c
        <
            srs::spar::detail::tuples_exists_if
                <
                    Params, srs::spar::detail::is_param<srs::spar::no_defs>::pred
                >::value,
            void,
            srs_spheroid_tag // WGS84
        >::type type;

    static const bool is_found = ! boost::is_same<type, void>::value;
    BOOST_MPL_ASSERT_MSG((is_found), UNKNOWN_ELLP_PARAM, (Params));
};

template <typename T>
inline void pj_calc_ellipsoid_params(parameters<T> & p, T const& a, T const& es) {
/****************************************************************************************
    Calculate a large number of ancillary ellipsoidal parameters, in addition to
    the two traditional PROJ defining parameters: Semimajor axis, a, and the
    eccentricity squared, es.

    Most of these parameters are fairly cheap to compute in comparison to the overall
    effort involved in initializing a PJ object. They may, however, take a substantial
    part of the time taken in computing an individual point transformation.

    So by providing them up front, we can amortize the (already modest) cost over all
    transformations carried out over the entire lifetime of a PJ object, rather than
    incur that cost for every single transformation.

    Most of the parameter calculations here are based on the "angular eccentricity",
    i.e. the angle, measured from the semiminor axis, of a line going from the north
    pole to one of the foci of the ellipsoid - or in other words: The arc sine of the
    eccentricity.

    The formulae used are mostly taken from:

    Richard H. Rapp: Geometric Geodesy, Part I, (178 pp, 1991).
    Columbus, Ohio:  Dept. of Geodetic Science
    and Surveying, Ohio State University.

****************************************************************************************/

    p.a = a;
    p.es = es;

    /* Compute some ancillary ellipsoidal parameters */
    if (p.e==0)
        p.e = sqrt(p.es);  /* eccentricity */
    //p.alpha = asin (p.e);  /* angular eccentricity */

    /* second eccentricity */
    //p.e2  = tan (p.alpha);
    //p.e2s = p.e2 * p.e2;

    /* third eccentricity */
    //p.e3    = (0!=p.alpha)? sin (p.alpha) / sqrt(2 - sin (p.alpha)*sin (p.alpha)): 0;
    //p.e3s = p.e3 * p.e3;

    /* flattening */
    //if (0==p.f)
    //    p.f  = 1 - cos (p.alpha);   /* = 1 - sqrt (1 - PIN->es); */
    //p.rf = p.f != 0.0 ? 1.0/p.f: HUGE_VAL;

    /* second flattening */
    //p.f2  = (cos(p.alpha)!=0)? 1/cos (p.alpha) - 1: 0;
    //p.rf2 = p.f2 != 0.0 ? 1/p.f2: HUGE_VAL;

    /* third flattening */
    //p.n  = pow (tan (p.alpha/2), 2);
    //p.rn = p.n != 0.0 ? 1/p.n: HUGE_VAL;

    /* ...and a few more */
    //if (0==p.b)
    //    p.b  = (1 - p.f)*p.a;
    //p.rb = 1. / p.b;
    p.ra = 1. / p.a;

    p.one_es = 1. - p.es;
    if (p.one_es == 0.) {
        BOOST_THROW_EXCEPTION( projection_exception(error_eccentricity_is_one) );
    }

    p.rone_es = 1./p.one_es;
}

} // namespace detail
}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_ELL_SET_HPP


#include <iostream>
#include <cassert>
#include <vector>
#include <map>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>

const int debug_level = 0;

#define DEBUG(min_level, x)      \
  if (debug_level >= min_level)  \
  {                              \
    std::cerr << x << std::endl; \
  }

typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS> Graph;

class DfsVisitor : public boost::default_dfs_visitor
{
public:
  DfsVisitor(std::vector<std::vector<int>> &jump_pointers_by_node) : jump_pointers_by_node(jump_pointers_by_node){};

  void discover_vertex(int node, const Graph &G)
  {
    for (int i = 1; i <= int(path.size()); i = i << 1)
    {
      jump_pointers_by_node.at(node).push_back(path.at(path.size() - i));
    }
    path.push_back(node);
  }

  void finish_vertex(int node, const Graph &G)
  {
    assert(!path.empty() && path.back() == node);
    path.pop_back();
  }

private:
  std::vector<int> path;
  std::vector<std::vector<int>> &jump_pointers_by_node;
};

void testcase()
{
  int n, q;
  std::cin >> n >> q;
  assert(n >= 1 && n <= 5e4 && q >= 1 && q <= 5e4);

  std::map<std::string, int> species_by_name;
  std::vector<std::string> names_by_node(n);
  std::vector<int> age_by_node(n);
  for (int i = 0; i < n; i++)
  {
    std::string name;
    int age;
    std::cin >> name >> age;
    assert(name.size() >= 1 && name.size() <= 10 && age >= 0 && age <= 1e9);
    species_by_name[name] = i;
    names_by_node.at(i) = name;
    age_by_node.at(i) = age;
  }

  Graph G(n);
  std::vector<bool> has_parent_by_node(n, false);
  for (int i = 1; i < n; i++)
  {
    std::string name_s, name_p;
    std::cin >> name_s >> name_p;
    const int s = species_by_name.at(name_s), p = species_by_name.at(name_p);
    assert(age_by_node.at(s) <= age_by_node.at(p));
    boost::add_edge(p, s, G);
    assert(!has_parent_by_node.at(s));
    has_parent_by_node.at(s) = true;
  }

  const int root = std::find(has_parent_by_node.begin(), has_parent_by_node.end(), false) - has_parent_by_node.begin();
  std::vector<std::vector<int>> jump_pointers_by_node(n);
  boost::depth_first_search(G, boost::root_vertex(root).visitor(DfsVisitor(jump_pointers_by_node)));

  for (int i = 0; i < q; i++)
  {
    std::string name_s;
    int b;
    std::cin >> name_s >> b;
    assert(b >= 0 && b <= 1e9);
    const int s = species_by_name.at(name_s);
    assert(age_by_node.at(s) <= b);

    int cursor = s;
    int j = std::numeric_limits<int>::max();
    while (true)
    {
      j = std::min(int(jump_pointers_by_node.at(cursor).size() - 1), j);
      if (j < 0)
      {
        break;
      }
      const int p = jump_pointers_by_node.at(cursor).at(j);
      if (age_by_node.at(p) > b)
      {
        j--;
        continue;
      }
      cursor = p;
    }

    if (i > 0)
    {
      std::cout << " ";
    }
    std::cout << names_by_node.at(cursor);
  }
  std::cout << "\n";
}

int main()
{
  std::ios_base::sync_with_stdio(false);

  int t;
  std::cin >> t;
  for (int i = 0; i < t; i++)
  {
    testcase();
    DEBUG(1, "");
  }

  return 0;
}


#include <iostream>
#include <math.h>
#include "properties.hpp"
#include "constants.hpp"
#include "bomd_rescale.hpp"
#include "qm_qchem.hpp"
//
#include <armadillo>
//

void
RescaleBomd::get_reader_data(ConfigBlockReader& reader) {
//    reader.get_data("additional_energy", additional_energy);
//    reader.get_data("step", step);
//    reader.get_data("dE", dE);
    (void) reader;
};

ConfigBlockReader
RescaleBomd::setup_reader() {
    //using types = ConfigBlockReader::types;
    std::cout << "Setup Rescale BOMD\n";
    ConfigBlockReader reader{"bomd-rescale"};
    //
    //reader.add_entry("active_state", 0);
    /*
    reader.add_entry("additional_energy", 1.0);
    reader.add_entry("dE", 0.01);
    reader.add_entry("step", 1);
    */
    return reader;
};

double
RescaleBomd::update_gradient()
{
    qm->update();

    /*
    PropMap props{};
    props.emplace(QMProperty::qmgradient, &qm_grd);
    props.emplace(QMProperty::mmgradient, &mm_grd);
    props.emplace(QMProperty::energies, &energy);
    //

    qm->get_properties(props);
    */
    //
    qm_grd.fill(0.0);
    mm_grd.fill(0.0);
    //
    double ene = 0.0; //energy[0];
    if (additional_energy > 0.0) {
       ene += additional_energy;
    }
    total_energy = ene;
    return ene;
};

double
kinetic_energy(arma::mat &velocities, arma::vec &masses) {
    double ekin = 0.0;
    for (arma::uword i=0; i<masses.n_elem; ++i) {
        for (arma::uword j=0; j<3; ++j) {
            ekin += 0.5 * velocities(j, i) * velocities(j, i)  * masses[i];
        }
    }
    return ekin;
}

bool RescaleBomd::rescale_velocities(arma::mat &velocities, arma::vec &masses, arma::mat &total_gradient, double e_drift) {
  static int ncall = 1;
  bool retval = false;

  (void) total_gradient;
  (void) e_drift;

  const double ekin = kinetic_energy(velocities, masses);
  const double factor = 1.0 + dE/ekin;
  //
  if ((ncall % step) == 0) {
    std::cout << "Rescale kinetic energy \n";
    std::cout << "factor: " << factor << "\n";
    if (additional_energy > dE) {
        std::cout << "we actually scale!\n";
        velocities *= sqrt(factor);
        additional_energy -= dE;
    }
    retval = true;
  }
  ncall += 1;
  std::cout << "Total energy " << total_energy << "au " << " ekin = " << ekin << "\n";
  return retval;
};


// Copyright 2015-2022 The ALMA Project Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

#pragma once

/// @file
///
/// Code implementing isotopic scattering according to Tamura's
/// formula:
/// S.-I. Tamura, Isotope scattering of dispersive phonons in Ge,
///    Phys. Rev. B 27 (1983) 858–866
/// A. Kundu, N. Mingo, D.A. Broido, D.A. Stewart, Role of light and
///    heavy embedded nanoparticles on the thermal conductivity of SiGe
///    alloys, Phys. Rev. B 84 (2011) 125426.
/// The same method is used for alloys in the virtual crystal
/// approximation.
/// The classes and functions in this file are modelled after those
/// declared in processes.hpp. There are, however, some differences.
/// The most important aspects are:
/// - Conservation of momentum is not enforced for two-phonon
/// processes.
/// - The Gaussian factor of each process is computed when the object
/// is built.
/// - The matrix element of each process is not stored in the object.

#include <cstddef>
#include <cmath>
#include <array>
#include <vector>
#include <boost/mpi.hpp>
#include <boost/math/distributions/normal.hpp>
#include <boost/serialization/array.hpp>
#include <structures.hpp>
#include <qpoint_grid.hpp>
#include <boost/math/distributions/normal.hpp>

// Forward declarations of elements documented later on.
// Note that serialization of std::vectors of objects without
// a default constructor is broken in Boost version 1.58.0. See:
// http://stackoverflow.com/a/30437359/85371
// https://svn.boost.org/trac/boost/ticket/11342
// This will prevent ALMA from compiling. We check for that
// specific version in our cmake configuration.

namespace alma {
/// Compute the 25th and 75th percentiles of log(sigma)
///
/// @param[in] sigma an Eigen array with all broadening parameters
/// @return an array containing the 25th and 75th percentiles
std::array<double, 2> calc_percentiles_log(
    const Eigen::Ref<const Eigen::ArrayXXd>& sigma);
/// Representation of a elastic two-phonon process.
class Twoph_process {
private:
    friend class boost::serialization::access;

    friend void save_bulk_hdf5(const char* filename,
                               const std::string& description,
                               const Crystal_structure& cell,
                               const Symmetry_operations& symmetries,
                               const Gamma_grid& grid,
                               const std::vector<Threeph_process>& processes,
                               const boost::mpi::communicator& comm);

    friend std::tuple<std::string,
                      std::unique_ptr<Crystal_structure>,
                      std::unique_ptr<Symmetry_operations>,
                      std::unique_ptr<Gamma_grid>,
                      std::unique_ptr<std::vector<Threeph_process>>>
    load_bulk_hdf5(const char* filename, const boost::mpi::communicator& comm);

    /// Deviation from the conservation of energy.
    double domega;
    /// Standard deviation of the Gaussian.
    double sigma;
    /// Gaussian factor of the process, coming from the
    /// regularized Dirac delta.
    double gaussian;

    /// serialization function
    template <class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        ar & this->domega;
        ar & this->sigma;
        ar & this->gaussian;
        ar & this->c;
        ar & this->q;
        ar & this->alpha;
    }

public:
    /// Equivalence class of the first phonon.
    std::size_t c;
    /// q point indices of each of the two phonons involved.
    std::array<std::size_t, 2> q;
    /// Mode indices of the two phonons involved.
    std::array<std::size_t, 2> alpha;
    /// Default constructor
    Twoph_process(){};
    /// Basic constructor.
    Twoph_process(std::size_t _c,
                  const std::array<std::size_t, 2>& _q,
                  const std::array<std::size_t, 2>& _alpha,
                  double _domega,
                  double _sigma)
        : domega(_domega), sigma(_sigma),
          gaussian(boost::math::pdf(boost::math::normal(0., sigma), domega)),
          c(_c), q(std::move(_q)), alpha(std::move(_alpha)) {
    }

    /// Copy constructor.
    Twoph_process(const Twoph_process& original)
        : domega(original.domega), sigma(original.sigma),
          gaussian(original.gaussian), c(original.c), q(original.q),
          alpha(original.alpha) {
    }
    /// Copy assignement operator
    Twoph_process& operator=(const Twoph_process& rhs) {
        using std::swap;
        this->domega = rhs.domega;
        this->sigma = rhs.sigma;
        this->gaussian = rhs.gaussian;
        this->c = rhs.c;
        this->q = rhs.q;
        this->alpha = rhs.alpha;
        return *this;
    }

    /// Move constructor:
    Twoph_process(Twoph_process&& rhs) {
        using std::swap;
        swap(this->domega, rhs.domega);
        swap(this->sigma, rhs.sigma);
        swap(this->gaussian, rhs.gaussian);
        swap(this->c, rhs.c);
        swap(this->q, rhs.q);
        swap(this->alpha, rhs.alpha);
    }

    /// Swap to enable swap semmantics
    void swap(Twoph_process& lhs, Twoph_process& rhs) {
        using std::swap;
        swap(this->domega, rhs.domega);
        swap(this->sigma, rhs.sigma);
        swap(this->gaussian, rhs.gaussian);
        swap(this->c, rhs.c);
        swap(this->q, rhs.q);
        swap(this->alpha, rhs.alpha);
    }

    /// Move assignement operator
    Twoph_process& operator=(Twoph_process&& rhs) {
        using std::swap;
        swap(this->domega, rhs.domega);
        swap(this->sigma, rhs.sigma);
        swap(this->gaussian, rhs.gaussian);
        swap(this->c, rhs.c);
        swap(this->q, rhs.q);
        swap(this->alpha, rhs.alpha);
        return *this;
    }

    /// Explicit constructor used when deserializing objects of
    /// this class. Save time by avoiding the calculatio of
    /// this->gaussian.
    Twoph_process(std::size_t _c,
                  const std::array<std::size_t, 2>& _q,
                  const std::array<std::size_t, 2>& _alpha,
                  double _domega,
                  double _sigma,
                  double _gaussian)
        : domega(_domega), sigma(_sigma), gaussian(_gaussian), c(_c),
          q(std::move(_q)), alpha(std::move(_alpha)) {
    }

    /// Get the "partial scattering rate" Gamma for this process.
    ///
    /// @param[in] cell - description of the unit cell
    /// @param[in] grid - regular grid with phonon spectrum
    /// @return Gamma, the partial scattering rate
    double compute_gamma(const Crystal_structure& cell,
                         const Gamma_grid& grid) const;

    /// Get the "partial scattering rate" Gamma for this process.
    /// using a custom set of g factors.
    ///
    /// @param[in] cell - description of the unit cell
    /// @param[in] gfactors - Pearson deviation coefficient of the mass
    /// at each site.
    /// @param[in] grid - regular grid with phonon spectrum
    /// @return Gamma, the partial scattering rate
    double compute_gamma(const Crystal_structure& cell,
                         const Eigen::Ref<const Eigen::VectorXd>& gfactors,
                         const Gamma_grid& grid) const;
};
/// Look for allowed two-phonon processes in a regular grid.
///
/// Iterate over part of the irreducible q points in the grid
/// (trying to evenly split the equivalence classes over processes)
/// and look for allowed two-phonon processes involving one phonon
/// from that part and another phonon from anywhere in the grid.
/// @param[in] grid - a regular grid containing Gamma
/// @param[in] communicator - MPI communicator to use
/// @param[in] scalebroad - factor modulating all the broadenings
/// @return a vector of Twoph_process objects
std::vector<Twoph_process> find_allowed_twoph(
    const Gamma_grid& grid,
    const boost::mpi::communicator& communicator,
    double scalebroad = 1.0);

/// Compute and the two-phonon contribution to the RTA
/// scattering rates for all vibrational modes on a grid.
///
/// @param[in] cell - a description of the crystal structure
/// @param[in] grid - phonon spectrum on a regular grid
/// @param[in] processes - a vector of allowed two-phonon processes
/// @param[in] comm - an mpi communicator
/// @return a set of scattering rates
Eigen::ArrayXXd calc_w0_twoph(const Crystal_structure& cell,
                              const alma::Gamma_grid& grid,
                              const std::vector<alma::Twoph_process>& processes,
                              const boost::mpi::communicator& comm);

/// Compute and the two-phonon contribution to the RTA
/// scattering rates for all vibrational modes on a grid, with a custom
/// set of g factors.
///
/// @param[in] cell - a description of the crystal structure
/// @param[in] grid - phonon spectrum on a regular grid
/// @param[in] gfactors - Pearson deviation coefficient of the mass
/// at each site.
/// @param[in] processes - a vector of allowed two-phonon processes
/// @param[in] comm - an mpi communicator
/// @return a set of scattering rates
Eigen::ArrayXXd calc_w0_twoph(const Crystal_structure& cell,
                              const Eigen::Ref<const Eigen::VectorXd>& gfactors,
                              const alma::Gamma_grid& grid,
                              const std::vector<alma::Twoph_process>& processes,
                              const boost::mpi::communicator& comm);
} // namespace alma


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "rbm_multival.hpp"

#include <Eigen/Dense>
#include <iostream>
#include <map>
#include <vector>
#include "Utils/all_utils.hpp"
#include "Utils/lookup.hpp"
#include "abstract_machine.hpp"
#include "rbm_spin.hpp"

namespace netket {

RbmMultival::RbmMultival(std::shared_ptr<const AbstractHilbert> hilbert,
                         int nhidden, int alpha, bool usea, bool useb)
    : AbstractMachine(hilbert),
      nv_(hilbert->Size()),
      ls_(hilbert->LocalSize()),
      usea_(usea),
      useb_(useb) {
  nh_ = std::max(nhidden, alpha * nv_);
  Init();
}

void RbmMultival::Init() {
  W_.resize(nv_ * ls_, nh_);
  a_.resize(nv_ * ls_);
  b_.resize(nh_);

  thetas_.resize(nh_);
  lnthetas_.resize(nh_);
  thetasnew_.resize(nh_);
  lnthetasnew_.resize(nh_);

  npar_ = nv_ * nh_ * ls_;

  if (usea_) {
    npar_ += nv_ * ls_;
  } else {
    a_.setZero();
  }

  if (useb_) {
    npar_ += nh_;
  } else {
    b_.setZero();
  }

  auto localstates = GetHilbert().LocalStates();

  localconfs_.resize(nv_ * ls_);
  for (int i = 0; i < nv_ * ls_; i += ls_) {
    for (int j = 0; j < ls_; j++) {
      localconfs_(i + j) = localstates[j];
    }
  }

  mask_.resize(nv_ * ls_, nv_);
  mask_.setZero();

  for (int i = 0; i < nv_ * ls_; i++) {
    mask_(i, i / ls_) = 1;
  }

  for (int i = 0; i < ls_; i++) {
    confindex_[localstates[i]] = i;
  }

  vtilde_.resize(nv_ * ls_);

  InfoMessage() << "RBM Multival Initizialized with nvisible = " << nv_
                << " and nhidden = " << nh_ << std::endl;
  InfoMessage() << "Using visible bias = " << usea_ << std::endl;
  InfoMessage() << "Using hidden bias  = " << useb_ << std::endl;
  InfoMessage() << "Local size is      = " << ls_ << std::endl;
}

int RbmMultival::Nvisible() const { return nv_; }

int RbmMultival::Npar() const { return npar_; }

void RbmMultival::InitRandomPars(int seed, double sigma) {
  VectorType par(npar_);

  netket::RandomGaussian(par, seed, sigma);

  SetParameters(par);
}

void RbmMultival::InitLookup(VisibleConstType v, LookupType &lt) {
  if (lt.VectorSize() == 0) {
    lt.AddVector(b_.size());
  }
  if (lt.V(0).size() != b_.size()) {
    lt.V(0).resize(b_.size());
  }
  ComputeTheta(v, lt.V(0));
}

void RbmMultival::UpdateLookup(VisibleConstType v,
                               const std::vector<int> &tochange,
                               const std::vector<double> &newconf,
                               LookupType &lt) {
  if (tochange.size() != 0) {
    for (std::size_t s = 0; s < tochange.size(); s++) {
      const int sf = tochange[s];
      const int oldtilde = confindex_[v[sf]];
      const int newtilde = confindex_[newconf[s]];

      lt.V(0) -= W_.row(ls_ * sf + oldtilde);
      lt.V(0) += W_.row(ls_ * sf + newtilde);
    }
  }
}

RbmMultival::VectorType RbmMultival::DerLog(VisibleConstType v) {
  LookupType ltnew;
  InitLookup(v, ltnew);
  return DerLog(v, ltnew);
}

RbmMultival::VectorType RbmMultival::DerLog(VisibleConstType v,
                                            const LookupType &lt) {
  VectorType der(npar_);
  der.setZero();

  ComputeVtilde(v, vtilde_);

  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      der(k) = vtilde_(k);
    }
  }

  RbmSpin::tanh(lt.V(0), lnthetas_);

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      der(k) = lnthetas_(p);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      der(k) = lnthetas_(j) * vtilde_(i);
      k++;
    }
  }
  return der;
}

RbmMultival::VectorType RbmMultival::GetParameters() {
  VectorType pars(npar_);

  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      pars(k) = a_(k);
    }
  }

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      pars(k) = b_(p);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      pars(k) = W_(i, j);
      k++;
    }
  }

  return pars;
}

void RbmMultival::SetParameters(VectorConstRefType pars) {
  int k = 0;

  if (usea_) {
    for (; k < nv_ * ls_; k++) {
      a_(k) = pars(k);
    }
  }

  if (useb_) {
    for (int p = 0; p < nh_; p++) {
      b_(p) = pars(k);
      k++;
    }
  }

  for (int i = 0; i < nv_ * ls_; i++) {
    for (int j = 0; j < nh_; j++) {
      W_(i, j) = pars(k);
      k++;
    }
  }
}

// Value of the logarithm of the wave-function
Complex RbmMultival::LogVal(VisibleConstType v) {
  ComputeTheta(v, thetas_);
  RbmSpin::lncosh(thetas_, lnthetas_);

  return (vtilde_.dot(a_) + lnthetas_.sum());
}

// Value of the logarithm of the wave-function
// using pre-computed look-up tables for efficiency
Complex RbmMultival::LogVal(VisibleConstType v, const LookupType &lt) {
  RbmSpin::lncosh(lt.V(0), lnthetas_);

  ComputeVtilde(v, vtilde_);
  return (vtilde_.dot(a_) + lnthetas_.sum());
}

// Difference between logarithms of values, when one or more visible variables
// are being changed
RbmMultival::VectorType RbmMultival::LogValDiff(
    VisibleConstType v, const std::vector<std::vector<int>> &tochange,
    const std::vector<std::vector<double>> &newconf) {
  const std::size_t nconn = tochange.size();
  VectorType logvaldiffs = VectorType::Zero(nconn);

  ComputeTheta(v, thetas_);
  RbmSpin::lncosh(thetas_, lnthetas_);

  Complex logtsum = lnthetas_.sum();

  for (std::size_t k = 0; k < nconn; k++) {
    if (tochange[k].size() != 0) {
      thetasnew_ = thetas_;

      for (std::size_t s = 0; s < tochange[k].size(); s++) {
        const int sf = tochange[k][s];
        const int oldtilde = confindex_[v[sf]];
        const int newtilde = confindex_[newconf[k][s]];

        logvaldiffs(k) -= a_(ls_ * sf + oldtilde);
        logvaldiffs(k) += a_(ls_ * sf + newtilde);

        thetasnew_ -= W_.row(ls_ * sf + oldtilde);
        thetasnew_ += W_.row(ls_ * sf + newtilde);
      }

      RbmSpin::lncosh(thetasnew_, lnthetasnew_);
      logvaldiffs(k) += lnthetasnew_.sum() - logtsum;
    }
  }
  return logvaldiffs;
}

// Difference between logarithms of values, when one or more visible variables
// are being changed Version using pre-computed look-up tables for efficiency
// on a small number of local changes
Complex RbmMultival::LogValDiff(VisibleConstType v,
                                const std::vector<int> &tochange,
                                const std::vector<double> &newconf,
                                const LookupType &lt) {
  Complex logvaldiff = 0.;

  if (tochange.size() != 0) {
    RbmSpin::lncosh(lt.V(0), lnthetas_);

    thetasnew_ = lt.V(0);

    for (std::size_t s = 0; s < tochange.size(); s++) {
      const int sf = tochange[s];
      const int oldtilde = confindex_[v[sf]];
      const int newtilde = confindex_[newconf[s]];

      logvaldiff -= a_(ls_ * sf + oldtilde);
      logvaldiff += a_(ls_ * sf + newtilde);

      thetasnew_ -= W_.row(ls_ * sf + oldtilde);
      thetasnew_ += W_.row(ls_ * sf + newtilde);
    }

    RbmSpin::lncosh(thetasnew_, lnthetasnew_);
    logvaldiff += (lnthetasnew_.sum() - lnthetas_.sum());
  }
  return logvaldiff;
}

#if 0
// Computhes the values of the theta pseudo-angles
inline void RbmMultival::ComputeTheta(VisibleConstType v, VectorType &theta) {
  ComputeVtilde(v, vtilde_);
  theta = (W_.transpose() * vtilde_ + b_);
}

inline void RbmMultival::ComputeVtilde(VisibleConstType v,
                                       Eigen::VectorXd &vtilde) {
  auto t = (localconfs_.array() == (mask_ * v).array());
  vtilde = t.template cast<double>();
}
#endif

void RbmMultival::Save(const std::string &filename) const {
  json state;
  state["Name"] = "RbmMultival";
  state["Nvisible"] = nv_;
  state["Nhidden"] = nh_;
  state["LocalSize"] = ls_;
  state["UseVisibleBias"] = usea_;
  state["UseHiddenBias"] = useb_;
  state["a"] = a_;
  state["b"] = b_;
  state["W"] = W_;
  WriteJsonToFile(state, filename);
}

void RbmMultival::Load(const std::string &filename) {
  auto const pars = ReadJsonFromFile(filename);
  if (pars.at("Name") != "RbmMultival") {
    throw InvalidInputError(
        "Error while constructing RbmMultival from Json input");
  }

  if (FieldExists(pars, "Nvisible")) {
    nv_ = pars["Nvisible"];
  }

  if (nv_ != GetHilbert().Size()) {
    throw InvalidInputError(
        "Loaded wave-function has incompatible Hilbert space");
  }

  if (FieldExists(pars, "LocalSize")) {
    ls_ = pars["LocalSize"];
  }
  if (ls_ != GetHilbert().LocalSize()) {
    throw InvalidInputError(
        "Loaded wave-function has incompatible Hilbert space");
  }

  if (FieldExists(pars, "Nhidden")) {
    nh_ = FieldVal(pars, "Nhidden");
  } else {
    nh_ = nv_ * double(FieldVal(pars, "Alpha"));
  }

  usea_ = FieldOrDefaultVal(pars, "UseVisibleBias", true);
  useb_ = FieldOrDefaultVal(pars, "UseHiddenBias", true);

  Init();

  // Loading parameters, if defined in the input
  if (FieldExists(pars, "a")) {
    a_ = pars["a"];
  } else {
    a_.setZero();
  }

  if (FieldExists(pars, "b")) {
    b_ = pars["b"];
  } else {
    b_.setZero();
  }
  if (FieldExists(pars, "W")) {
    W_ = pars["W"];
  }
}

bool RbmMultival::IsHolomorphic() const noexcept { return true; }

}  // namespace netket


#include <iostream>
#include <string>

#include <boost/program_options.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <amgcl/backend/builtin.hpp>
#include <amgcl/relaxation/runtime.hpp>
#include <amgcl/coarsening/runtime.hpp>
#include <amgcl/coarsening/rigid_body_modes.hpp>
#include <amgcl/solver/runtime.hpp>
#include <amgcl/preconditioner/runtime.hpp>
#include <amgcl/deflated_solver.hpp>
#include <amgcl/amg.hpp>
#include <amgcl/adapter/crs_tuple.hpp>
#include <amgcl/adapter/reorder.hpp>
#include <amgcl/io/mm.hpp>
#include <amgcl/io/binary.hpp>

#include <amgcl/profiler.hpp>

namespace amgcl { profiler<> prof; }
using amgcl::prof;
using amgcl::precondition;

//---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
    namespace po = boost::program_options;
    namespace io = amgcl::io;

    using amgcl::prof;
    using std::vector;
    using std::string;

    po::options_description desc("Options");

    desc.add_options()
        ("help,h", "Show this help.")
        ("prm-file,P",
         po::value<string>(),
         "Parameter file in json format. "
        )
        (
         "prm,p",
         po::value< vector<string> >()->multitoken(),
         "Parameters specified as name=value pairs. "
         "May be provided multiple times. Examples:\n"
         "  -p solver.tol=1e-3\n"
         "  -p precond.coarse_enough=300"
        )
        ("matrix,A",
         po::value<string>()->required(),
         "System matrix in the MatrixMarket format."
        )
        (
         "rhs,f",
         po::value<string>(),
         "The RHS vector in the MatrixMarket format. "
         "When omitted, a vector of ones is used by default. "
         "Should only be provided together with a system matrix. "
        )
        (
         "scale,s",
         po::bool_switch()->default_value(false),
         "Scale the matrix so that the diagonal is unit. "
        )
        (
         "null,N",
         po::value<string>(),
         "Starting null-vectors in the MatrixMarket format. "
        )
        (
         "numvec,n",
         po::value<int>()->default_value(3),
         "The number of near nullspace vectors to search for. "
        )
        (
         "binary,B",
         po::bool_switch()->default_value(false),
         "When specified, treat input files as binary instead of as MatrixMarket. "
         "It is assumed the files were converted to binary format with mm2bin utility. "
        )
        (
         "output,o",
         po::value<string>(),
         "Output the computed nullspace to the MatrixMarket file."
        )
        ;

    po::positional_options_description p;
    p.add("prm", -1);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
    po::notify(vm);

    if (vm.count("help")) {
        std::cout << desc << std::endl;
        return 0;
    }

    for (int i = 0; i < argc; ++i) {
        if (i) std::cout << " ";
        std::cout << argv[i];
    }
    std::cout << std::endl;

    boost::property_tree::ptree prm;
    if (vm.count("prm-file")) {
        read_json(vm["prm-file"].as<string>(), prm);
    }

    if (vm.count("prm")) {
        for(const string &v : vm["prm"].as<vector<string> >()) {
            amgcl::put(prm, v);
        }
    }

    ptrdiff_t rows, nv = 0, numvec = vm["numvec"].as<int>();
    vector<ptrdiff_t> ptr, col;
    vector<double> val, rhs;
    std::list<std::vector<double>> Z;

    {
        auto t = prof.scoped_tic("read");

        string Afile  = vm["matrix"].as<string>();
        bool   binary = vm["binary"].as<bool>();

        if (binary) {
            io::read_crs(Afile, rows, ptr, col, val);
        } else {
            ptrdiff_t cols;
            std::tie(rows, cols) = io::mm_reader(Afile)(ptr, col, val);
            precondition(rows == cols, "Non-square system matrix");
        }

        if (vm.count("rhs")) {
            string bfile = vm["rhs"].as<string>();

            ptrdiff_t n, m;

            if (binary) {
                io::read_dense(bfile, n, m, rhs);
            } else {
                std::tie(n, m) = io::mm_reader(bfile)(rhs);
            }

            precondition(n == rows && m == 1, "The RHS vector has wrong size");
        } else {
            rhs.resize(rows, 1.0);
        }

        if (vm.count("null")) {
            string nfile = vm["null"].as<string>();

            std::vector<double> null;
            ptrdiff_t m;

            if (binary) {
                io::read_dense(nfile, m, nv, null);
            } else {
                std::tie(m, nv) = io::mm_reader(nfile)(null);
            }

            precondition(m == rows, "Near null-space vectors have wrong size");

            for(ptrdiff_t i = 0; i < nv; ++i) {
                Z.emplace_back(rows);
                for(ptrdiff_t j = 0; j < rows; ++j) {
                    Z.back()[j] = null[j * nv + i];
                }
            }
        }
    }

    if (vm["scale"].as<bool>()) {
        auto t = prof.scoped_tic("scaling");
        std::vector<double> dia(rows, 1.0);

        for (ptrdiff_t i = 0; i < static_cast<ptrdiff_t>(rows); ++i) {
            double d = 1.0;
            for(ptrdiff_t j = ptr[i], e = ptr[i+1]; j < e; ++j) {
                if (col[j] == i) {
                    d = 1 / sqrt(val[j]);
                }
            }
            if (!std::isnan(d)) dia[i] = d;
        }

        for (ptrdiff_t i = 0; i < static_cast<ptrdiff_t>(rows); ++i) {
            rhs[i] *= dia[i];
            for(ptrdiff_t j = ptr[i], e = ptr[i+1]; j < e; ++j) {
                val[j] *= dia[i] * dia[col[j]];
            }
        }
    }

    typedef amgcl::backend::builtin<double> Backend;
    typedef amgcl::make_solver<
        amgcl::amg<
            Backend,
            amgcl::runtime::coarsening::wrapper,
            amgcl::runtime::relaxation::wrapper
            >,
        amgcl::runtime::solver::wrapper<Backend>
        > Solver;

    std::mt19937 rng;
    std::uniform_real_distribution<double> rnd(-1, 1);
    std::vector<double> x(rows), zero(rows, 0.0);

    auto A = std::tie(rows, ptr, col, val);

    prm.put("solver.ns_search", true);

    prof.tic("search");
    for(int k = nv; k < numvec; ++k) {
        auto t = prof.scoped_tic(std::string("vector ") + std::to_string(k));
        std::vector<double> N;

        if (k) {
            N.resize(k * rows);
            int j = 0;
            for(const auto &z : Z) {
                for(ptrdiff_t i = 0; i < rows; ++i) {
                    N[i * k + j] = z[i];
                }
                ++j;
            }

            prm.put("precond.coarsening.nullspace.rows", rows);
            prm.put("precond.coarsening.nullspace.cols", k);
            prm.put("precond.coarsening.nullspace.B", N.data());
        }

        prof.tic("setup");
        Solver S(A, prm);
        prof.toc("setup");

        std::cout << std::endl
                  << "-------------------------" << std::endl
                  << "-- Searching for vector " << k << std::endl
                  << "-------------------------" << std::endl
                  << S << std::endl;

        for(auto &v : x) v = rnd(rng);

        int iters;
        double error;

        prof.tic("solve");
        std::tie(iters, error) = S(zero, x);
        prof.toc("solve");

        std::cout << "Iterations: " << iters << std::endl
                  << "Error:      " << error << std::endl;

        // Orthonormalize the new vector
        for(const auto &z : Z) {
            double c = amgcl::backend::inner_product(x,z) / amgcl::backend::inner_product(z,z);
            amgcl::backend::axpby(-c, z, 1, x);
        }

        double nx = sqrt(amgcl::backend::inner_product(x, x));
        for(auto &v : x) v /= nx;
        Z.push_back(x);
    }
    prof.toc("search");

    // Solve the system using the near nullspace vectors:
    std::vector<double> N(numvec * rows);
    {
        auto t = prof.scoped_tic("apply");

        int j = 0;
        for(const auto &z : Z) {
            for(ptrdiff_t i = 0; i < rows; ++i) {
                N[i * numvec + j] = z[i];
            }
            ++j;
        }

        prm.put("precond.coarsening.nullspace.rows", rows);
        prm.put("precond.coarsening.nullspace.cols", numvec);
        prm.put("precond.coarsening.nullspace.B", N.data());

        prof.tic("setup");
        Solver S(A, prm);
        prof.toc("setup");

        std::cout << std::endl
                  << "-------------------------" << std::endl
                  << "-- Solving the system " << std::endl
                  << "-------------------------" << std::endl
                  << S << std::endl;

        amgcl::backend::clear(x);

        int iters;
        double error;

        prof.tic("solve");
        std::tie(iters, error) = S(rhs, x);
        prof.toc("solve");

        std::cout << "Iterations: " << iters << std::endl
            << "Error:      " << error << std::endl;
    }

    if (vm.count("output")) {
        auto t = prof.scoped_tic("write");
        amgcl::io::mm_write(vm["output"].as<string>(), N.data(), rows, numvec);
    }

    std::cout << prof << std::endl;
}


// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Darrell Wright
//
// 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 <boost/scoped_array.hpp>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <Wt/WApplication>

#include "channelcombiner.h"
#include <daw/grayscale_filter/genericimage.h>
#include "largepixeldata.h"

namespace daw { namespace imaging {
	namespace {
		float too_gs_small( float const & R, float const & G, float const & B ) {
			return 0.299f*R + 0.587f*G + 0.114f*B;
		}

		float round( float const & value ) {
			return floor( value + 0.5f );
		}

		uint8_t max( rgb3 lpd ) {
			int ret = lpd.red;
			if( lpd.green > ret ) {
				ret = lpd.green;
			}
			if( lpd.blue > ret ) {
				ret = lpd.blue;
			}
			return ret;
		}

		template<typename T>
		auto max3( T value1, T const & value2, T const & value3 ) {
			if( value2 > value1 ) {
				value1 = value2;
			}
			if( value3 > value1 ) {
				value1 = value3;
			}
			return value1;
		}

		auto max3( LargePixelData const & pd ) {
			return max3( pd.R, pd.G, pd.B );
		}

		template<typename T>
		auto min3( T value1, T const & value2, T const & value3 ) {
			if( value2 < value1 ) {
				value1 = value2;
			}
			if( value3 < value1 ) {
				value1 = value3;
			}
			return value1;
		}

		auto min3( LargePixelData const & pd ) {
			return min3( pd.R, pd.G, pd.B );
		}

		void clampvalue( int &value, int const min, int const max ) {
			if( value < min ) {
				Wt::log( "info" ) << "Had to clamp value from from " << value << " to " << min;
				value = min;
			} else if( value > max ) {
				Wt::log( "info" ) << "Had to clamp value from from " << value << " to " << max;
				value = max;
			}
		}

		void clampvalue( LargePixelData &value, int const min, int const max ) {
			clampvalue( value.R, min, max );
			clampvalue( value.G, min, max );
			clampvalue( value.B, min, max );
		}

		float colform( const rgb3 c, float const R, float const G, float const B ) {
			return R*(float)c.red + G*(float)c.green + B*(float)c.blue;
		}
	}
	GenericImage<rgb3> ChannelCombiner::runfilter( GenericImage<rgb3> const & image_y, GenericImage<rgb3> const & image_u, GenericImage<rgb3> const & image_v ) const {
		if( !((image_y.size( ) == image_v.size( )) && (image_y.size( ) == image_v.size( ))) ) {
			const std::string msg = "Images are not the same size in channel filter";
			Wt::log( "error" ) << msg;
			throw std::runtime_error( msg );
		}

		boost::scoped_array<LargePixelData> output_lpdimg( new LargePixelData[image_y.size( )] );	//output_lpdimg
		for( size_t n=0; n<image_y.size( ); ++n ) {
			float const Y = (float)image_y[n].red;
			float const U = (float)image_u[n].red;
			float const V = (float)image_v[n].red;
			output_lpdimg[n] = LargePixelData( (int)(Y + 1.14f*V), (int)(Y - 0.395f*U - 0.581f*V), (int)(Y + 2.032f*U) );
		}

		LargePixelData pd_min( std::numeric_limits<int>::max( ), std::numeric_limits<int>::max( ), std::numeric_limits<int>::max( ) );
		LargePixelData pd_max( std::numeric_limits<int>::min( ), std::numeric_limits<int>::min( ), std::numeric_limits<int>::min( ) );

		// Do not parallelize without accounting for shared data
		for( size_t n=0; n<image_y.size( ); ++n ) {
			LargePixelData::min( output_lpdimg[n], pd_min );
			LargePixelData::max( output_lpdimg[n], pd_max );
		}

		auto const max_all = max3( pd_max );
		auto const min_all = min3( pd_min );
		auto const range_all = static_cast<float>(max_all - min_all);

		GenericImage<rgb3> output_image{ image_y.width( ), image_y.height( ) };

		//#pragma omp parallel for
		for( int n=0; n<(int)image_y.size( ); ++n ) {
			LargePixelData cur_value;
			cur_value.R = (int)((float)(output_lpdimg[n].R-pd_min.R)*(255.0f/range_all));
			cur_value.G = (int)((float)(output_lpdimg[n].G-pd_min.G)*(255.0f/range_all));
			cur_value.B = (int)((float)(output_lpdimg[n].B-pd_min.B)*(255.0f/range_all));
			clampvalue( cur_value, 0, 255 );
			output_image[n] = rgb3( (unsigned char)cur_value.R, (unsigned char)cur_value.G, (unsigned char)cur_value.B );

		}

		return output_image;
	}
}}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

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

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

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_SOMERC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_SOMERC_HPP

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

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/aasincos.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct somerc {}; // Swiss. Obl. Mercator

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace somerc
    {
            static const double epsilon = 1.e-10;
            static const int n_iter = 6;

            template <typename T>
            struct par_somerc
            {
                T K, c, hlf_e, kR, cosp0, sinp0;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_somerc_ellipsoid
                : public base_t_fi<base_somerc_ellipsoid<T, Parameters>, T, Parameters>
            {
                par_somerc<T> m_proj_parm;

                inline base_somerc_ellipsoid(const Parameters& par)
                    : base_t_fi<base_somerc_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(e_forward)
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T fourth_pi = detail::fourth_pi<T>();
                    static const T half_pi = detail::half_pi<T>();

                    T phip, lamp, phipp, lampp, sp, cp;

                    sp = this->m_par.e * sin(lp_lat);
                    phip = 2.* atan( exp( this->m_proj_parm.c * (
                        log(tan(fourth_pi + 0.5 * lp_lat)) - this->m_proj_parm.hlf_e * log((1. + sp)/(1. - sp)))
                        + this->m_proj_parm.K)) - half_pi;
                    lamp = this->m_proj_parm.c * lp_lon;
                    cp = cos(phip);
                    phipp = aasin(this->m_proj_parm.cosp0 * sin(phip) - this->m_proj_parm.sinp0 * cp * cos(lamp));
                    lampp = aasin(cp * sin(lamp) / cos(phipp));
                    xy_x = this->m_proj_parm.kR * lampp;
                    xy_y = this->m_proj_parm.kR * log(tan(fourth_pi + 0.5 * phipp));
                }

                // INVERSE(e_inverse)  ellipsoid & spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T fourth_pi = detail::fourth_pi<T>();

                    T phip, lamp, phipp, lampp, cp, esp, con, delp;
                    int i;

                    phipp = 2. * (atan(exp(xy_y / this->m_proj_parm.kR)) - fourth_pi);
                    lampp = xy_x / this->m_proj_parm.kR;
                    cp = cos(phipp);
                    phip = aasin(this->m_proj_parm.cosp0 * sin(phipp) + this->m_proj_parm.sinp0 * cp * cos(lampp));
                    lamp = aasin(cp * sin(lampp) / cos(phip));
                    con = (this->m_proj_parm.K - log(tan(fourth_pi + 0.5 * phip)))/this->m_proj_parm.c;
                    for (i = n_iter; i ; --i) {
                        esp = this->m_par.e * sin(phip);
                        delp = (con + log(tan(fourth_pi + 0.5 * phip)) - this->m_proj_parm.hlf_e *
                            log((1. + esp)/(1. - esp)) ) *
                            (1. - esp * esp) * cos(phip) * this->m_par.rone_es;
                        phip -= delp;
                        if (fabs(delp) < epsilon)
                            break;
                    }
                    if (i) {
                        lp_lat = phip;
                        lp_lon = lamp / this->m_proj_parm.c;
                    } else {
                        BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                    }
                }

                static inline std::string get_name()
                {
                    return "somerc_ellipsoid";
                }

            };

            // Swiss. Obl. Mercator
            template <typename Parameters, typename T>
            inline void setup_somerc(Parameters& par, par_somerc<T>& proj_parm)
            {
                static const T fourth_pi = detail::fourth_pi<T>();

                T cp, phip0, sp;

                proj_parm.hlf_e = 0.5 * par.e;
                cp = cos(par.phi0);
                cp *= cp;
                proj_parm.c = sqrt(1 + par.es * cp * cp * par.rone_es);
                sp = sin(par.phi0);
                proj_parm.cosp0 = cos( phip0 = aasin(proj_parm.sinp0 = sp / proj_parm.c) );
                sp *= par.e;
                proj_parm.K = log(tan(fourth_pi + 0.5 * phip0)) - proj_parm.c * (
                    log(tan(fourth_pi + 0.5 * par.phi0)) - proj_parm.hlf_e *
                    log((1. + sp) / (1. - sp)));
                proj_parm.kR = par.k0 * sqrt(par.one_es) / (1. - sp * sp);
            }

    }} // namespace detail::somerc
    #endif // doxygen

    /*!
        \brief Swiss. Obl. Mercator projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Ellipsoid
         - For CH1903
        \par Example
        \image html ex_somerc.gif
    */
    template <typename T, typename Parameters>
    struct somerc_ellipsoid : public detail::somerc::base_somerc_ellipsoid<T, Parameters>
    {
        inline somerc_ellipsoid(const Parameters& par) : detail::somerc::base_somerc_ellipsoid<T, Parameters>(par)
        {
            detail::somerc::setup_somerc(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::somerc, somerc_ellipsoid, somerc_ellipsoid)

        // Factory entry(s)
        template <typename T, typename Parameters>
        class somerc_entry : public detail::factory_entry<T, Parameters>
        {
            public :
                virtual base_v<T, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<somerc_ellipsoid<T, Parameters>, T, Parameters>(par);
                }
        };

        template <typename T, typename Parameters>
        inline void somerc_init(detail::base_factory<T, Parameters>& factory)
        {
            factory.add_to_factory("somerc", new somerc_entry<T, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_SOMERC_HPP


/*
 * Copyright (c) 2011, Mattia Penati <mattia.penati@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Politecnico di Milano 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.
 */

#ifndef AMA_TENSOR_MUL_MUL_TEMPORARY_HPP
#define AMA_TENSOR_MUL_MUL_TEMPORARY_HPP 1

#include <ama/tensor/iexp/index_reorder.hpp>
#include <ama/tensor/iexp/iexp_base.hpp>
#include <ama/tensor/mul/mul_calculator.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/plus.hpp>

/* this class is used to store a temporary expression before the reduction */

namespace ama
{
  namespace tensor_
  {

    /* forward declaration */
    template <typename LEFT, typename RIGHT> class mul_temporary;

    /* specialization of iexp traits */
    template <typename LEFT, typename RIGHT>
    struct iexp_traits< mul_temporary<LEFT, RIGHT> >
    {
      typedef typename LEFT::value_type value_type;

      typedef typename LEFT::dimension_type dimension_type;

      typedef typename mul_index<LEFT, RIGHT>::controvariant_list controvariant_list;
      typedef typename mul_index<LEFT, RIGHT>::covariant_list covariant_list;

      typedef ::boost::mpl::false_ is_assignable;
    };

    /* class declaration */
    template <typename LEFT, typename RIGHT>
    class mul_temporary:
        public iexp_base< mul_temporary<LEFT, RIGHT> >
    {
    protected:
      typedef iexp_base< mul_temporary<LEFT, RIGHT> > base_type;

      typedef LEFT left_operand;
      typedef RIGHT right_operand;

    public:
      /* constructor */
      mul_temporary(left_operand const & left,
                    right_operand const & right)
          : m_left(left),
            m_right(right) { }

    public:
      typedef typename base_type::value_type value_type;

      /* retrieve the value */
      template <typename IMAP>
      value_type at() const
      {
        return m_left.template at<IMAP>() * m_right.template at<IMAP>();
      }

    protected:
      left_operand const m_left;
      right_operand const m_right;
    };

  }
}

#endif /* AMA_TENSOR_MUL_MUL_TEMPORARY_HPP */


#include <algorithm>
#include <functional>
#include <string>
#include <random>

#include <boost/math/constants/constants.hpp>
#include <Euclid/Util/Assert.h>

namespace Euclid
{

namespace _impl
{

inline void mask_filter(const RTCFilterFunctionNArguments* args)
{
    EASSERT(args->N == 1);
    auto mask = reinterpret_cast<uint8_t*>(args->geometryUserPtr);
    EASSERT(mask != nullptr);

    auto primid = RTCHitN_primID(args->hit, 1, 0);
    if (mask[primid] != 1) { args->valid[0] = 0; }
}

} // namespace _impl

inline RayCamera::RayCamera(const Vec3& position,
                            const Vec3& focus,
                            const Vec3& up,
                            float tnear,
                            float tfar)
    : Camera(position, focus, up, tnear, tfar)
{}

inline PerspRayCamera::PerspRayCamera(const Vec3& position,
                                      const Vec3& focus,
                                      const Vec3& up,
                                      float vfov,
                                      float aspect,
                                      float tnear,
                                      float tfar)
    : RayCamera(position, focus, up, tnear, tfar)
{
    auto fov = vfov * boost::math::float_constants::degree;
    this->film.height = 2.0f * std::tan(fov * 0.5f);
    this->film.width = aspect * this->film.height;
}

inline PerspRayCamera::PerspRayCamera(const Vec3& position,
                                      const Vec3& focus,
                                      const Vec3& up,
                                      float vfov,
                                      unsigned width,
                                      unsigned height,
                                      float tnear,
                                      float tfar)
    : RayCamera(position, focus, up, tnear, tfar)
{
    auto fov = vfov * boost::math::float_constants::degree;
    auto aspect = static_cast<float>(width) / height;
    this->film.height = 2.0f * std::tan(fov * 0.5f);
    this->film.width = aspect * film.height;
}

inline PerspRayCamera::~PerspRayCamera() = default;

inline void PerspRayCamera::set_aspect(float aspect)
{
    this->film.width = aspect * this->film.height;
}

inline void PerspRayCamera::set_aspect(unsigned width, unsigned height)
{
    auto aspect = static_cast<float>(width) / height;
    this->film.width = aspect * this->film.height;
}

inline void PerspRayCamera::set_fov(float vfov)
{
    auto fov = vfov * boost::math::float_constants::degree;
    auto aspect = this->film.width / this->film.height;
    this->film.height = 2.0f * std::tan(fov * 0.5f);
    this->film.width = aspect * this->film.height;
}

inline RTCRayHit PerspRayCamera::gen_ray(float s, float t) const
{
    RTCRayHit rayhit;
    Eigen::Vector3f view = -this->dir + (s - 0.5f) * this->film.width * u +
                           (t - 0.5f) * this->film.height * v;
    EASSERT(view.dot(this->dir) < 0.0f);

    rayhit.ray.org_x = this->pos(0);
    rayhit.ray.org_y = this->pos(1);
    rayhit.ray.org_z = this->pos(2);
    rayhit.ray.dir_x = view(0);
    rayhit.ray.dir_y = view(1);
    rayhit.ray.dir_z = view(2);
    rayhit.ray.tnear = this->tnear;
    rayhit.ray.tfar = this->tfar;
    rayhit.ray.flags = 0;
    rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
    return rayhit;
}

inline OrthoRayCamera::OrthoRayCamera(const Vec3& position,
                                      const Vec3& focus,
                                      const Vec3& up,
                                      float xextent,
                                      float yextent,
                                      float tnear,
                                      float tfar)
    : RayCamera(position, focus, up, tnear, tfar)
{
    this->film.width = xextent;
    this->film.height = yextent;
}

inline OrthoRayCamera::~OrthoRayCamera() = default;

inline void OrthoRayCamera::set_extent(float xextent, float yextent)
{
    this->film.width = xextent;
    this->film.height = yextent;
}

inline RTCRayHit OrthoRayCamera::gen_ray(float s, float t) const
{
    RTCRayHit rayhit;
    Eigen::Vector3f origin = this->pos + (s - 0.5f) * this->film.width * u +
                             (t - 0.5f) * this->film.height * v;

    rayhit.ray.org_x = origin(0);
    rayhit.ray.org_y = origin(1);
    rayhit.ray.org_z = origin(2);
    rayhit.ray.dir_x = -this->dir(0);
    rayhit.ray.dir_y = -this->dir(1);
    rayhit.ray.dir_z = -this->dir(2);
    rayhit.ray.tnear = this->tnear;
    rayhit.ray.tfar = this->tfar;
    rayhit.ray.flags = 0;
    rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
    return rayhit;
}

inline RayTracer::RayTracer(int threads)
{
    std::string cfg("threads=");
    cfg.append(std::to_string(threads));
    _device = rtcNewDevice(cfg.c_str());
    if (!_device) {
        auto err = rtcGetDeviceError(_device);
        std::string err_str("Embree device creation error: ");
        err_str.append(std::to_string(err));
        throw std::runtime_error(err_str);
    }

    _scene = rtcNewScene(_device);
    if (!_scene) {
        auto err = rtcGetDeviceError(_device);
        std::string err_str("Embree scene creation error: ");
        err_str.append(std::to_string(err));
        throw std::runtime_error(err_str);
    }

    _material.ambient << 0.1f, 0.1f, 0.1f;
    _material.diffuse << 0.7f, 0.7f, 0.7f;
}

inline RayTracer::~RayTracer()
{
    release_buffers();
    rtcReleaseScene(_scene);
    rtcReleaseDevice(_device);
}

// TODO: add generic type support
inline void RayTracer::attach_geometry_buffers(
    const std::vector<float>& positions,
    const std::vector<unsigned>& indices)
{
    if (positions.empty() || indices.empty()) {
        EWARNING("Input geometry is empty.");
        return;
    }
    if (positions.size() % 3 != 1) {
        throw std::invalid_argument("The last element of the positions buffer "
                                    "is not padded to 16 bytes. Add "
                                    "one more 0.0f to your positions buffer.");
    }
    if (indices.size() % 3 != 0) {
        throw std::invalid_argument("Size of input indices is not divisible by "
                                    "3, thus not a valid triangle mesh.");
    }

    release_buffers();

    _geometry = rtcNewGeometry(_device, RTC_GEOMETRY_TYPE_TRIANGLE);
    rtcSetSharedGeometryBuffer(_geometry,
                               RTC_BUFFER_TYPE_VERTEX,
                               0,
                               RTC_FORMAT_FLOAT3,
                               positions.data(),
                               0,
                               3 * sizeof(float),
                               positions.size() / 3);
    rtcSetSharedGeometryBuffer(_geometry,
                               RTC_BUFFER_TYPE_INDEX,
                               0,
                               RTC_FORMAT_UINT3,
                               indices.data(),
                               0,
                               3 * sizeof(unsigned),
                               indices.size() / 3);
    rtcCommitGeometry(_geometry);
    _geom_id = rtcAttachGeometry(_scene, _geometry);
    rtcCommitScene(_scene);
}

inline void RayTracer::attach_color_buffer(const std::vector<float>* colors,
                                           bool vertex_color)
{
    if (colors && colors->empty()) {
        EWARNING("Input geometry is empty.");
        return;
    }
    if (colors && vertex_color && colors->size() % 3 != 1) {
        throw std::invalid_argument("The last element of the colors buffer "
                                    "is not padded to 16 bytes. Add "
                                    "one more 0.0f to your colors buffer.");
    }

    // ! vertex color -> vertex color
    if (!(_colors && _vertex_color) && (colors && vertex_color)) {
        rtcSetGeometryVertexAttributeCount(_geometry, 1);
        rtcSetSharedGeometryBuffer(_geometry,
                                   RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE,
                                   0,
                                   RTC_FORMAT_FLOAT3,
                                   colors->data(),
                                   0,
                                   3 * sizeof(float),
                                   colors->size() / 3);
        rtcCommitGeometry(_geometry);
    }
    // vertex color -> ! vertex color
    else if ((_colors && _vertex_color) && !(colors && vertex_color)) {
        rtcSetGeometryVertexAttributeCount(_geometry, 0);
        rtcCommitGeometry(_geometry);
    }
    // vertex color -> vertex color
    else if ((_colors && _vertex_color) && (colors && vertex_color)) {
        rtcSetSharedGeometryBuffer(_geometry,
                                   RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE,
                                   0,
                                   RTC_FORMAT_FLOAT3,
                                   colors->data(),
                                   0,
                                   3 * sizeof(float),
                                   colors->size() / 3);
        rtcCommitGeometry(_geometry);
    }

    _colors = colors;
    _vertex_color = vertex_color;
}

inline void RayTracer::attach_face_mask_buffer(const std::vector<uint8_t>* mask)
{
    // no mask -> mask
    if (!_face_mask && mask) {
        rtcSetGeometryIntersectFilterFunction(_geometry, _impl::mask_filter);
    }
    // mask -> no mask
    else if (_face_mask && !mask) {
        rtcSetGeometryIntersectFilterFunction(_geometry, nullptr);
    }

    uint8_t* buffer = nullptr;
    if (mask) { buffer = const_cast<uint8_t*>(mask->data()); }
    rtcSetGeometryUserData(_geometry, buffer);
    rtcCommitGeometry(_geometry);
    _face_mask = mask;
}

inline void RayTracer::release_buffers()
{
    if (_geom_id != -1) {
        rtcDetachGeometry(_scene, _geom_id);
        _geom_id = -1;
        _colors = nullptr;
        _face_mask = nullptr;
        rtcReleaseGeometry(_geometry);
    }
}

inline void RayTracer::set_material(const Material& material)
{
    _material = material;
}

inline void RayTracer::set_background(
    const Eigen::Ref<const Eigen::Array3f>& color)
{
    _background = color;
}

inline void RayTracer::set_background(float r, float g, float b)
{
    _background << r, g, b;
}

inline void RayTracer::enable_light(bool on)
{
    _lighting = on;
}

inline void RayTracer::render_shaded(std::vector<uint8_t>& pixels,
                                     const RayCamera& camera,
                                     int width,
                                     int height,
                                     bool interleaved)
{
    pixels.resize(3 * width * height);
    auto diffuse_color = _select_diffuse_color();

    RTCIntersectContext context;
    rtcInitIntersectContext(&context);

#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            Eigen::Array3f color(0.0f, 0.0f, 0.0f);
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcIntersect1(_scene, &context, &rayhit);

            if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                Eigen::Array3f ambient = _material.ambient;
                Eigen::Array3f diffuse = diffuse_color(rayhit.hit);
                if (_lighting) {
                    Eigen::Vector3f normal = Eigen::Vector3f(rayhit.hit.Ng_x,
                                                             rayhit.hit.Ng_y,
                                                             rayhit.hit.Ng_z)
                                                 .normalized();
                    // // Point light at the view position
                    Eigen::Vector3f lightdir = Eigen::Vector3f(rayhit.ray.dir_x,
                                                               rayhit.ray.dir_y,
                                                               rayhit.ray.dir_z)
                                                   .normalized();
                    diffuse *= std::abs(normal.dot(-lightdir));
                }
                color += ambient + diffuse;
            }
            else {
                color += _background;
            }
            color(0) = std::min(color(0), 1.0f);
            color(1) = std::min(color(1), 1.0f);
            color(2) = std::min(color(2), 1.0f);
            color *= 255;
            auto r = static_cast<uint8_t>(color(0));
            auto g = static_cast<uint8_t>(color(1));
            auto b = static_cast<uint8_t>(color(2));
            if (interleaved) {
                pixels[3 * ((height - y - 1) * width + x) + 0] = r;
                pixels[3 * ((height - y - 1) * width + x) + 1] = g;
                pixels[3 * ((height - y - 1) * width + x) + 2] = b;
            }
            else {
                pixels[(height - y - 1) * width + x] = r;
                pixels[width * height + (height - y - 1) * width + x] = g;
                pixels[2 * width * height + (height - y - 1) * width + x] = b;
            }
        }
    }
}

inline void RayTracer::render_shaded(std::vector<uint8_t>& pixels,
                                     const RayCamera& camera,
                                     int width,
                                     int height,
                                     int samples,
                                     bool interleaved)
{
    pixels.resize(3 * width * height);
    auto diffuse_color = _select_diffuse_color();

    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
    std::random_device rd;
    std::minstd_rand rd_gen(rd());
    std::uniform_real_distribution<> rd_number(0.0f, 1.0f);
    const float rcpr_samples = 1.0f / samples;

#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            Eigen::Array3f color(0.0f, 0.0f, 0.0f);
            for (int s = 0; s < samples; ++s) {
                auto u = static_cast<float>(x + rd_number(rd_gen)) / width;
                auto v = static_cast<float>(y + rd_number(rd_gen)) / height;
                auto rayhit = camera.gen_ray(u, v);
                rtcIntersect1(_scene, &context, &rayhit);

                if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                    Eigen::Array3f ambient = _material.ambient;
                    Eigen::Array3f diffuse = diffuse_color(rayhit.hit);
                    if (_lighting) {
                        Eigen::Vector3f normal =
                            Eigen::Vector3f(rayhit.hit.Ng_x,
                                            rayhit.hit.Ng_y,
                                            rayhit.hit.Ng_z)
                                .normalized();
                        // // Point light at the view position
                        Eigen::Vector3f lightdir =
                            Eigen::Vector3f(rayhit.ray.dir_x,
                                            rayhit.ray.dir_y,
                                            rayhit.ray.dir_z)
                                .normalized();
                        diffuse *= std::abs(normal.dot(-lightdir));
                    }
                    color += ambient + diffuse;
                }
                else {
                    color += _background;
                }
            }
            color *= rcpr_samples;
            color(0) = std::min(color(0), 1.0f);
            color(1) = std::min(color(1), 1.0f);
            color(2) = std::min(color(2), 1.0f);
            color *= 255;
            auto r = static_cast<uint8_t>(color(0));
            auto g = static_cast<uint8_t>(color(1));
            auto b = static_cast<uint8_t>(color(2));
            if (interleaved) {
                pixels[3 * ((height - y - 1) * width + x) + 0] = r;
                pixels[3 * ((height - y - 1) * width + x) + 1] = g;
                pixels[3 * ((height - y - 1) * width + x) + 2] = b;
            }
            else {
                pixels[(height - y - 1) * width + x] = r;
                pixels[width * height + (height - y - 1) * width + x] = g;
                pixels[2 * width * height + (height - y - 1) * width + x] = b;
            }
        }
    }
}

inline void RayTracer::render_depth(std::vector<uint8_t>& pixels,
                                    const RayCamera& camera,
                                    int width,
                                    int height)
{
    pixels.resize(width * height);
    std::vector<float> depths(width * height, -1.0f);
    float min_depth = std::numeric_limits<float>::max();
    float max_depth = -1.0f;
    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcIntersect1(_scene, &context, &rayhit);
            if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                auto depth = rayhit.ray.tfar * camera.dir.norm();
                depths[(height - y - 1) * width + x] = depth;
                if (depth < min_depth) min_depth = depth;
                if (depth > max_depth) max_depth = depth;
            }
        }
    }
    float denom = 1.0f / (max_depth - min_depth);
    for (size_t i = 0; i < depths.size(); ++i) {
        if (depths[i] == -1.0f) { pixels[i] = 0; }
        else {
            pixels[i] =
                static_cast<uint8_t>((max_depth - depths[i]) * denom * 255);
        }
    }
}

inline void RayTracer::render_depth(std::vector<float>& values,
                                    const RayCamera& camera,
                                    int width,
                                    int height)
{
    values.resize(width * height);
    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcIntersect1(_scene, &context, &rayhit);
            if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                auto depth = rayhit.ray.tfar * camera.dir.norm();
                values[(height - y - 1) * width + x] = depth;
            }
            else {
                values[(height - y - 1) * width + x] = -1.0f;
            }
        }
    }
}

inline void RayTracer::render_silhouette(std::vector<uint8_t>& pixels,
                                         const RayCamera& camera,
                                         int width,
                                         int height)
{
    pixels.resize(width * height);
    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcOccluded1(_scene, &context, &(rayhit.ray));
            if (rayhit.ray.tfar <= 0.0f) {
                pixels[(height - y - 1) * width + x] = 255;
            }
            else {
                pixels[(height - y - 1) * width + x] = 0;
            }
        }
    }
}

inline void RayTracer::render_index(std::vector<uint8_t>& pixels,
                                    const RayCamera& camera,
                                    int width,
                                    int height,
                                    bool interleaved)
{
    pixels.resize(3 * width * height);
    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcIntersect1(_scene, &context, &rayhit);
            uint32_t index;
            if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                index = rayhit.hit.primID + 1;
            }
            else {
                index = 0;
            }
            uint8_t r = index & 0x000000FF;
            uint8_t g = (index >> 8) & 0x000000FF;
            uint8_t b = (index >> 16) & 0x000000FF;
            if (interleaved) {
                pixels[3 * ((height - y - 1) * width + x) + 0] = r;
                pixels[3 * ((height - y - 1) * width + x) + 1] = g;
                pixels[3 * ((height - y - 1) * width + x) + 2] = b;
            }
            else {
                pixels[(height - y - 1) * width + x] = r;
                pixels[width * height + (height - y - 1) * width + x] = g;
                pixels[2 * width * height + (height - y - 1) * width + x] = b;
            }
        }
    }
}

inline void RayTracer::render_index(std::vector<uint32_t>& indices,
                                    const RayCamera& camera,
                                    int width,
                                    int height)
{
    indices.resize(width * height);
    RTCIntersectContext context;
    rtcInitIntersectContext(&context);
#pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            auto u = static_cast<float>(x) / width;
            auto v = static_cast<float>(y) / height;
            auto rayhit = camera.gen_ray(u, v);
            rtcIntersect1(_scene, &context, &rayhit);
            uint32_t index;
            if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) {
                index = rayhit.hit.primID + 1;
            }
            else {
                index = 0;
            }
            indices[(height - y - 1) * width + x] = index;
        }
    }
}

inline std::function<Eigen::Array3f(const RTCHit&)>
RayTracer::_select_diffuse_color()
{
    if (_colors && _vertex_color) {
        return std::bind(
            &RayTracer::_diffuse_vertex_color, this, std::placeholders::_1);
    }
    else if (_colors && !_vertex_color) {
        return std::bind(
            &RayTracer::_diffuse_face_color, this, std::placeholders::_1);
    }
    else {
        return std::bind(
            &RayTracer::_diffuse_material, this, std::placeholders::_1);
    }
}

inline Eigen::Array3f RayTracer::_diffuse_material(const RTCHit& hit)
{
    (void)hit;
    return _material.diffuse;
}

inline Eigen::Array3f RayTracer::_diffuse_face_color(const RTCHit& hit)
{
    Eigen::Array3f diffuse;
    diffuse << (*_colors)[3 * hit.primID], (*_colors)[3 * hit.primID + 1],
        (*_colors)[3 * hit.primID + 2];
    return diffuse;
}

inline Eigen::Array3f RayTracer::_diffuse_vertex_color(const RTCHit& hit)
{
    float buffer[4];
    RTCInterpolateArguments args;
    args.geometry = _geometry;
    args.primID = hit.primID;
    args.u = hit.u;
    args.v = hit.v;
    args.bufferType = RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE;
    args.bufferSlot = 0;
    args.valueCount = 3;
    args.P = buffer;
    args.dPdu = nullptr;
    args.dPdv = nullptr;
    args.ddPdudu = nullptr;
    args.ddPdvdv = nullptr;
    args.ddPdudv = nullptr;
    rtcInterpolate(&args);

    Eigen::Array3f diffuse;
    diffuse << buffer[0], buffer[1], buffer[2];
    return diffuse;
}

} // namespace Euclid