// Boost.Geometry

// Copyright (c) 2021, Oracle and/or its affiliates.

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

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_DISCRETE_DISTANCE_GEOGRAPHIC_HPP
#define BOOST_GEOMETRY_STRATEGIES_DISCRETE_DISTANCE_GEOGRAPHIC_HPP

#include <boost/geometry/strategies/detail.hpp>
#include <boost/geometry/strategies/discrete_distance/services.hpp>
#include <boost/geometry/strategies/distance/comparable.hpp>
#include <boost/geometry/strategies/distance/detail.hpp>

#include <boost/geometry/strategies/geographic/distance.hpp>
// TODO - for backwards compatibility, remove?
#include <boost/geometry/strategies/geographic/distance_andoyer.hpp>
#include <boost/geometry/strategies/geographic/distance_thomas.hpp>
#include <boost/geometry/strategies/geographic/distance_vincenty.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace discrete_distance
{

template
<
    typename FormulaPolicy = strategy::andoyer,
    typename Spheroid = srs::spheroid<double>,
    typename CalculationType = void
>
class geographic
    : public strategies::detail::geographic_base<Spheroid>
{
    using base_t = strategies::detail::geographic_base<Spheroid>;

public:
    geographic() = default;

    explicit geographic(Spheroid const& spheroid)
        : base_t(spheroid)
    {}

    template <typename Geometry1, typename Geometry2>
    auto distance(Geometry1 const&, Geometry2 const&,
                  distance::detail::enable_if_pp_t<Geometry1, Geometry2> * = nullptr) const
    {
        return strategy::distance::geographic
                <
                    FormulaPolicy, Spheroid, CalculationType
                >(base_t::m_spheroid);
    }
};

namespace services
{

template <typename Geometry1, typename Geometry2>
struct default_strategy<Geometry1, Geometry2, geographic_tag, geographic_tag>
{
    using type = strategies::discrete_distance::geographic<>;
};

template <typename FP, typename S, typename CT>
struct strategy_converter<strategy::distance::geographic<FP, S, CT> >
{
    static auto get(strategy::distance::geographic<FP, S, CT> const& s)
    {
        return strategies::discrete_distance::geographic<FP, S, CT>(s.model());
    }
};
// TODO - for backwards compatibility, remove?
template <typename S, typename CT>
struct strategy_converter<strategy::distance::andoyer<S, CT> >
{
    static auto get(strategy::distance::andoyer<S, CT> const& s)
    {
        return strategies::discrete_distance::geographic<strategy::andoyer, S, CT>(s.model());
    }
};
// TODO - for backwards compatibility, remove?
template <typename S, typename CT>
struct strategy_converter<strategy::distance::thomas<S, CT> >
{
    static auto get(strategy::distance::thomas<S, CT> const& s)
    {
        return strategies::discrete_distance::geographic<strategy::thomas, S, CT>(s.model());
    }
};
// TODO - for backwards compatibility, remove?
template <typename S, typename CT>
struct strategy_converter<strategy::distance::vincenty<S, CT> >
{
    static auto get(strategy::distance::vincenty<S, CT> const& s)
    {
        return strategies::discrete_distance::geographic<strategy::vincenty, S, CT>(s.model());
    }
};

} // namespace services

}} // namespace strategies::discrete_distance

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_DISCRETE_DISTANCE_GEOGRAPHIC_HPP


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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland.

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

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

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGY_AGNOSTIC_POINT_IN_POLY_WINDING_HPP
#define BOOST_GEOMETRY_STRATEGY_AGNOSTIC_POINT_IN_POLY_WINDING_HPP

#include <boost/core/ignore_unused.hpp>

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

#include <boost/geometry/strategies/side.hpp>
#include <boost/geometry/strategies/covered_by.hpp>
#include <boost/geometry/strategies/within.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace within
{

// 1 deg or pi/180 rad
template <typename Point,
          typename CalculationType = typename coordinate_type<Point>::type>
struct winding_small_angle
{
    typedef typename coordinate_system<Point>::type cs_t;
    typedef math::detail::constants_on_spheroid
        <
            CalculationType,
            typename cs_t::units
        > constants;

    static inline CalculationType apply()
    {
        return constants::half_period() / CalculationType(180);
    }
};

// Fix for https://svn.boost.org/trac/boost/ticket/9628
// For floating point coordinates, the <D> coordinate of a point is compared
// with the segment's points using some EPS. If the coordinates are "equal"
// the sides are calculated. Therefore we can treat a segment as a long areal
// geometry having some width. There is a small ~triangular area somewhere
// between the segment's effective area and a segment's line used in sides
// calculation where the segment is on the one side of the line but on the
// other side of a segment (due to the width).
// Below picture assuming D = 1, if D = 0 horiz<->vert, E<->N, RIGHT<->UP.
// For the s1 of a segment going NE the real side is RIGHT but the point may
// be detected as LEFT, like this:
//                     RIGHT
//                 ___----->
//                  ^      O Pt  __ __
//                 EPS     __ __
//                  v__ __ BUT DETECTED AS LEFT OF THIS LINE
//             _____7
//       _____/
// _____/
// In the code below actually D = 0, so segments are nearly-vertical
// Called when the point is on the same level as one of the segment's points
// but the point is not aligned with a vertical segment
template <typename CSTag>
struct winding_side_equal
{
    typedef typename strategy::side::services::default_strategy
        <
            CSTag
        >::type strategy_side_type;

    template <typename Point, typename PointOfSegment>
    static inline int apply(Point const& point,
                            PointOfSegment const& se,
                            int count)
    {
        typedef typename coordinate_type<PointOfSegment>::type scoord_t;
        typedef typename coordinate_system<PointOfSegment>::type::units units_t;

        if (math::equals(get<1>(point), get<1>(se)))
            return 0;

        // Create a horizontal segment intersecting the original segment's endpoint
        // equal to the point, with the derived direction (E/W).
        PointOfSegment ss1, ss2;
        set<1>(ss1, get<1>(se));
        set<0>(ss1, get<0>(se));
        set<1>(ss2, get<1>(se));
        scoord_t ss20 = get<0>(se);
        if (count > 0)
        {
            ss20 += winding_small_angle<PointOfSegment>::apply();
        }
        else
        {
            ss20 -= winding_small_angle<PointOfSegment>::apply();
        }
        math::normalize_longitude<units_t>(ss20);
        set<0>(ss2, ss20);

        // Check the side using this vertical segment
        return strategy_side_type::apply(ss1, ss2, point);
    }
};
// The optimization for cartesian
template <>
struct winding_side_equal<cartesian_tag>
{
    template <typename Point, typename PointOfSegment>
    static inline int apply(Point const& point,
                            PointOfSegment const& se,
                            int count)
    {
        // NOTE: for D=0 the signs would be reversed
        return math::equals(get<1>(point), get<1>(se)) ?
                0 :
                get<1>(point) < get<1>(se) ?
                    // assuming count is equal to 1 or -1
                    -count : // ( count > 0 ? -1 : 1) :
                    count;   // ( count > 0 ? 1 : -1) ;
    }
};

template <typename Point,
          typename CalculationType,
          typename CSTag = typename cs_tag<Point>::type>
struct winding_check_touch
{
    typedef CalculationType calc_t;
    typedef typename coordinate_system<Point>::type::units units_t;
    typedef math::detail::constants_on_spheroid<CalculationType, units_t> constants;

    template <typename PointOfSegment, typename State>
    static inline int apply(Point const& point,
                            PointOfSegment const& seg1,
                            PointOfSegment const& seg2,
                            State& state,
                            bool& eq1,
                            bool& eq2)
    {
        calc_t const pi = constants::half_period();
        calc_t const pi2 = pi / calc_t(2);

        calc_t const px = get<0>(point);
        calc_t const s1x = get<0>(seg1);
        calc_t const s2x = get<0>(seg2);
        calc_t const py = get<1>(point);
        calc_t const s1y = get<1>(seg1);
        calc_t const s2y = get<1>(seg2);

        // NOTE: lat in {-90, 90} and arbitrary lon
        //  it doesn't matter what lon it is if it's a pole
        //  so e.g. if one of the segment endpoints is a pole
        //  then only the other lon matters

        bool eq1_strict = math::equals(s1x, px);
        bool eq2_strict = math::equals(s2x, px);

        eq1 = eq1_strict // lon strictly equal to s1
           || math::equals(s1y, pi2) || math::equals(s1y, -pi2); // s1 is pole
        eq2 = eq2_strict // lon strictly equal to s2
           || math::equals(s2y, pi2) || math::equals(s2y, -pi2); // s2 is pole

        // segment overlapping pole
        calc_t s1x_anti = s1x + constants::half_period();
        math::normalize_longitude<units_t, calc_t>(s1x_anti);
        bool antipodal = math::equals(s2x, s1x_anti);
        if (antipodal)
        {
            eq1 = eq2 = eq1 || eq2;

            // segment overlapping pole and point is pole
            if (math::equals(py, pi2) || math::equals(py, -pi2))
            {
                eq1 = eq2 = true;
            }
        }

        // Both equal p -> segment vertical
        // The only thing which has to be done is check if point is ON segment
        if (eq1 && eq2)
        {
            // segment endpoints on the same sides of the globe
            if (! antipodal
                // p's lat between segment endpoints' lats
                ? (s1y <= py && s2y >= py) || (s2y <= py && s1y >= py)
                // going through north or south pole?
                : (pi - s1y - s2y <= pi
                    ? (eq1_strict && s1y <= py) || (eq2_strict && s2y <= py) // north
                        || math::equals(py, pi2) // point on north pole
                    : (eq1_strict && s1y >= py) || (eq2_strict && s2y >= py)) // south
                        || math::equals(py, -pi2) // point on south pole
                )
            {
                state.m_touches = true;
            }
            return true;
        }
        return false;
    }
};
// The optimization for cartesian
template <typename Point, typename CalculationType>
struct winding_check_touch<Point, CalculationType, cartesian_tag>
{
    typedef CalculationType calc_t;

    template <typename PointOfSegment, typename State>
    static inline bool apply(Point const& point,
                             PointOfSegment const& seg1,
                             PointOfSegment const& seg2,
                             State& state,
                             bool& eq1,
                             bool& eq2)
    {
        calc_t const px = get<0>(point);
        calc_t const s1x = get<0>(seg1);
        calc_t const s2x = get<0>(seg2);

        eq1 = math::equals(s1x, px);
        eq2 = math::equals(s2x, px);

        // Both equal p -> segment vertical
        // The only thing which has to be done is check if point is ON segment
        if (eq1 && eq2)
        {
            calc_t const py = get<1>(point);
            calc_t const s1y = get<1>(seg1);
            calc_t const s2y = get<1>(seg2);
            if ((s1y <= py && s2y >= py) || (s2y <= py && s1y >= py))
            {
                state.m_touches = true;
            }
            return true;
        }
        return false;
    }
};

// Called if point is not aligned with a vertical segment
template <typename Point,
          typename CalculationType,
          typename CSTag = typename cs_tag<Point>::type>
struct winding_calculate_count
{
    typedef CalculationType calc_t;
    typedef typename coordinate_system<Point>::type::units units_t;

    static inline bool greater(calc_t const& l, calc_t const& r)
    {
        calc_t diff = l - r;
        math::normalize_longitude<units_t, calc_t>(diff);
        return diff > calc_t(0);
    }

    static inline int apply(calc_t const& p,
                            calc_t const& s1, calc_t const& s2,
                            bool eq1, bool eq2)
    {
        // Probably could be optimized by avoiding normalization for some comparisons
        // e.g. s1 > p could be calculated from p > s1

        // If both segment endpoints were poles below checks wouldn't be enough
        // but this means that either both are the same or that they are N/S poles
        // and therefore the segment is not valid.
        // If needed (eq1 && eq2 ? 0) could be returned

        return
              eq1 ? (greater(s2, p) ?  1 : -1)      // Point on level s1, E/W depending on s2
            : eq2 ? (greater(s1, p) ? -1 :  1)      // idem
            : greater(p, s1) && greater(s2, p) ?  2 // Point between s1 -> s2 --> E
            : greater(p, s2) && greater(s1, p) ? -2 // Point between s2 -> s1 --> W
            : 0;
    }
};
// The optimization for cartesian
template <typename Point, typename CalculationType>
struct winding_calculate_count<Point, CalculationType, cartesian_tag>
{
    typedef CalculationType calc_t;

    static inline int apply(calc_t const& p,
                            calc_t const& s1, calc_t const& s2,
                            bool eq1, bool eq2)
    {
        return
              eq1 ? (s2 > p ?  1 : -1)  // Point on level s1, E/W depending on s2
            : eq2 ? (s1 > p ? -1 :  1)  // idem
            : s1 < p && s2 > p ?  2     // Point between s1 -> s2 --> E
            : s2 < p && s1 > p ? -2     // Point between s2 -> s1 --> W
            : 0;
    }
};

/*!
\brief Within detection using winding rule
\ingroup strategies
\tparam Point \tparam_point
\tparam PointOfSegment \tparam_segment_point
\tparam SideStrategy Side strategy
\tparam CalculationType \tparam_calculation
\author Barend Gehrels
\note The implementation is inspired by terralib http://www.terralib.org (LGPL)
\note but totally revised afterwards, especially for cases on segments
\note Only dependant on "side", -> agnostic, suitable for spherical/latlong

\qbk{
[heading See also]
[link geometry.reference.algorithms.within.within_3_with_strategy within (with strategy)]
}
 */
template
<
    typename Point,
    typename PointOfSegment = Point,
    typename SideStrategy = typename strategy::side::services::default_strategy
                                <
                                    typename cs_tag<Point>::type
                                >::type,
    typename CalculationType = void
>
class winding
{
    typedef typename select_calculation_type
        <
            Point,
            PointOfSegment,
            CalculationType
        >::type calculation_type;

    /*! subclass to keep state */
    class counter
    {
        int m_count;
        bool m_touches;

        inline int code() const
        {
            return m_touches ? 0 : m_count == 0 ? -1 : 1;
        }

    public :
        friend class winding;

        template <typename P, typename CT, typename CST>
        friend struct winding_check_touch;

        inline counter()
            : m_count(0)
            , m_touches(false)
        {}

    };

    static inline int check_segment(Point const& point,
                PointOfSegment const& seg1, PointOfSegment const& seg2,
                counter& state, bool& eq1, bool& eq2)
    {
        if (winding_check_touch<Point, calculation_type>
                ::apply(point, seg1, seg2, state, eq1, eq2))
        {
            return 0;
        }

        calculation_type const p = get<0>(point);
        calculation_type const s1 = get<0>(seg1);
        calculation_type const s2 = get<0>(seg2);
        return winding_calculate_count<Point, calculation_type>
                    ::apply(p, s1, s2, eq1, eq2);
    }

public:
    winding()
    {}

    explicit winding(SideStrategy const& side_strategy)
        : m_side_strategy(side_strategy)
    {}

    // Typedefs and static methods to fulfill the concept
    typedef Point point_type;
    typedef PointOfSegment segment_point_type;
    typedef counter state_type;

    inline bool apply(Point const& point,
                      PointOfSegment const& s1, PointOfSegment const& s2,
                      counter& state) const
    {
        typedef typename cs_tag<Point>::type cs_t;

        bool eq1 = false;
        bool eq2 = false;
        boost::ignore_unused(eq2);

        int count = check_segment(point, s1, s2, state, eq1, eq2);
        if (count != 0)
        {
            int side = 0;
            if (count == 1 || count == -1)
            {
                side = winding_side_equal<cs_t>::apply(point, eq1 ? s1 : s2, count);
            }
            else // count == 2 || count == -2
            {
                // 1 left, -1 right
                side = m_side_strategy.apply(s1, s2, point);
            }

            if (side == 0)
            {
                // Point is lying on segment
                state.m_touches = true;
                state.m_count = 0;
                return false;
            }

            // Side is NEG for right, POS for left.
            // The count is -2 for down, 2 for up (or -1/1)
            // Side positive thus means UP and LEFTSIDE or DOWN and RIGHTSIDE
            // See accompagnying figure (TODO)
            if (side * count > 0)
            {
                state.m_count += count;
            }
        }
        return ! state.m_touches;
    }

    static inline int result(counter const& state)
    {
        return state.code();
    }

private:
    SideStrategy m_side_strategy;
};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace services
{

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, polygonal_tag, cartesian_tag, cartesian_tag>
{
    typedef winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, polygonal_tag, spherical_tag, spherical_tag>
{
    typedef winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, linear_tag, cartesian_tag, cartesian_tag>
{
    typedef winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, linear_tag, spherical_tag, spherical_tag>
{
    typedef winding<Point, typename geometry::point_type<Geometry>::type> type;
};

} // namespace services

#endif

}} // namespace strategy::within

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
namespace strategy { namespace covered_by { namespace services
{

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, polygonal_tag, cartesian_tag, cartesian_tag>
{
    typedef strategy::within::winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, polygonal_tag, spherical_tag, spherical_tag>
{
    typedef strategy::within::winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, linear_tag, cartesian_tag, cartesian_tag>
{
    typedef strategy::within::winding<Point, typename geometry::point_type<Geometry>::type> type;
};

template <typename Point, typename Geometry, typename AnyTag>
struct default_strategy<Point, Geometry, point_tag, AnyTag, pointlike_tag, linear_tag, spherical_tag, spherical_tag>
{
    typedef strategy::within::winding<Point, typename geometry::point_type<Geometry>::type> type;
};

}}} // namespace strategy::covered_by::services
#endif

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGY_AGNOSTIC_POINT_IN_POLY_WINDING_HPP


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

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

#ifndef BOOST_SIMD_FUNCTION_SCALAR_ACSCH_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SCALAR_ACSCH_HPP_INCLUDED

#include <boost/simd/function/definition/acsch.hpp>
#include <boost/simd/arch/common/scalar/function/acsch.hpp>

#endif


#include <iostream>
#include <fstream>
#include <iomanip>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
#include "RayTrace.h"
#include "RayTrace_IceModels.h"

struct underline{
	static char esc;
	const std::string& str;
	underline(const std::string& s):str(s){}
	friend std::ostream& operator<<(std::ostream& os, const underline& u);
};

char underline::esc=0x1B;

std::ostream& operator<<(std::ostream& os, const underline& u){
	return(os << underline::esc << "[4m" << u.str << underline::esc << "[0m");
}

template<typename positionType>
class pathPrinter{
public:
	pathPrinter(){}
	void operator()(const positionType& p, RayTrace::RKStepType stepType){
		std::cout << p.x << ' ' << p.z << '\n';
	}
};

int main(int argc, char* argv[]){
	double ns,nd,nc;
	double src_x, src_y, src_z, trg_x, trg_y, trg_z;
	Vector src,trg;
	bool showLabels=true;
	bool dumpPaths=false;
	bool surface_reflect;
	bool bedrock_reflect;
	double requiredAccuracy;
	double frequency;
	double polarization;
	boost::shared_ptr<RayTrace::indexOfRefractionModel> refractionModel;
	std::string refractionName;
	boost::shared_ptr<RayTrace::attenuationModel> attenuationModel;
	std::string attenuationName;

	boost::program_options::options_description desc("Allowed options");
	desc.add_options()
		("help,h","print usage information")
		("src_x",boost::program_options::value<double>(&src_x),"source x coordinate")
		("src_y",boost::program_options::value<double>(&src_y),"source y coordinate")
		("src_z",boost::program_options::value<double>(&src_z),"source z coordinate")
		("trg_x",boost::program_options::value<double>(&trg_x),"target x coordinate")
		("trg_y",boost::program_options::value<double>(&trg_y),"target y coordinate")
		("trg_z",boost::program_options::value<double>(&trg_z),"target z coordinate")
		("quiet,q","quiet, hide column labels")
		("show_paths,p","show paths; print out x and z coordinates of points visited along each path. Suppresses ordinary output")
		("n_s",boost::program_options::value<double>(&ns)->default_value(1.35),"surface index of refraction")
		("n_d",boost::program_options::value<double>(&nd)->default_value(1.78),"deep index of refraction")
		("n_c",boost::program_options::value<double>(&nc)->default_value(.0132),"index of refraction transition coefficient")
		("reflect_surface",boost::program_options::value<bool>(&surface_reflect)->default_value(true),"whether to search for surface\nreflected solutions")
		("reflect_bedrock",boost::program_options::value<bool>(&bedrock_reflect)->default_value(false),"whether to search for bedrock\nreflected solutions")
		("accuracy",boost::program_options::value<double>(&requiredAccuracy)->default_value(0.1),"the maximum acceptable vertical miss distance in meters")
		("frequency",boost::program_options::value<double>(&frequency)->default_value(300),"the frequency of the signal, in MHz")
		("polarization",boost::program_options::value<double>(&polarization)->default_value(RayTrace::pi/2),"the angle of the signal polarization, relative to the plane of propagation, in radians")
		("index_of_refraction",boost::program_options::value<std::string>(&refractionName)->default_value("exponential"),
		 "the index of refraction function of the ice, recognized values are 'exponential', 'inverse_exponential', 'quadratic', 'todor_linear', 'todor_chi', and 'todor_LL'")
		("attenuation",boost::program_options::value<std::string>(&attenuationName)->default_value("besson"),"the attenuation function of the ice, recognized values are 'negligible' and 'besson'")
	;
	boost::program_options::positional_options_description p;
	p.add("src_x", 1);
	p.add("src_y", 1);
	p.add("src_z", 1);
	p.add("trg_x", 1);
	p.add("trg_y", 1);
	p.add("trg_z", 1);

	boost::program_options::variables_map vm;
	try{
		boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
		boost::program_options::notify(vm);
	}catch(std::exception& except){
		std::cerr << "Caught an exception during argument parsing: " << except.what() << std::endl;
		return(1);
	}catch(...){
		std::cerr << "An unknown exception was caught during argument parsing" << std::endl;
		return(1);
	}

	if (vm.count ("help") || argc < 2) {
		std::cout << "Usage: ray_solver [OPTION]... " << underline("src.x") << ' ' << underline("src.y") << ' ' << underline("src.z") << ' '
		<< underline("trg.x") << ' ' << underline("trg.y") << ' ' << underline("trg.z") << std::endl;
		std::cout << desc << std::endl;
		std::cout << "Angles are measured in radians from the downward vertical" << std::endl;
		std::cout << "The reported attenuation includes the effects of the ice attenuation model and reflections." << std::endl;
		std::cout << "The (electric field) amplitude value reported combines the attenuation with the divergence of rays." << std::endl;
		return(0);
	}
	if(vm.count("quiet"))
	   showLabels=false;
	if(vm.count("show_paths")){
		dumpPaths=true;
		showLabels=false;
	}
	if(refractionName=="exponential")
		refractionModel=boost::shared_ptr<exponentialRefractiveIndex>(new exponentialRefractiveIndex(ns,nd,nc));
		//refractionModel=boost::make_shared<exponentialRefractiveIndex>(ns,nd,nc);
	else if(refractionName=="inverse_exponential")
		refractionModel=boost::shared_ptr<inverseExponentialRefractiveIndex>(new inverseExponentialRefractiveIndex(ns,nd,nc));
		//refractionModel=boost::make_shared<inverseExponentialRefractiveIndex>(ns,nd,nc);
	else if(refractionName=="quadratic")
		refractionModel=boost::shared_ptr<quadraticRefractiveIndex>(new quadraticRefractiveIndex(ns,nd,nc));
		//refractionModel=boost::make_shared<quadraticRefractiveIndex>(ns,nd,nc);
	else if(refractionName=="todor_linear")
		refractionModel=boost::shared_ptr<todorLinearRefractiveIndex>(new todorLinearRefractiveIndex(ns,nd));
		//refractionModel=boost::make_shared<todorLinearRefractiveIndex>(ns,nd);
	else if(refractionName=="todor_chi")
		refractionModel=boost::shared_ptr<todorChiRefractiveIndex>(new todorChiRefractiveIndex(ns,nd));
		//refractionModel=boost::make_shared<todorChiRefractiveIndex>(ns,nd);
	else if(refractionName=="todor_LL")
		refractionModel=boost::shared_ptr<todorLLRefractiveIndex>(new todorLLRefractiveIndex(ns,nd));
		//refractionModel=boost::make_shared<todorLLRefractiveIndex>(ns,nd);
	else{
		std::cerr << "Unrecognized refraction model name '" << refractionName << "'" << std::endl;
		return(1);
	}
	if(attenuationName=="besson")
		attenuationModel=boost::shared_ptr<basicAttenuationModel>(new basicAttenuationModel);
		//attenuationModel=boost::make_shared<basicAttenuationModel>();
	else if(attenuationName=="negligible")
		attenuationModel=boost::shared_ptr<negligibleAttenuationModel>(new negligibleAttenuationModel);
		//attenuationModel=boost::make_shared<negligibleAttenuationModel>();
	else{
		std::cerr << "Unrecognized attenuation model name '" << attenuationName << "'" << std::endl;
		return(1);
	}

	unsigned short refl = RayTrace::NoReflection;
	if(surface_reflect)
		refl|=RayTrace::SurfaceReflection;
	if(bedrock_reflect)
		refl|=RayTrace::BedrockReflection;

	src.SetX(src_x);
	src.SetY(src_y);
	src.SetZ(src_z);
	trg.SetX(trg_x);
	trg.SetY(trg_y);
	trg.SetZ(trg_z);

	std::vector<RayTrace::TraceRecord> paths;

	RayTrace::TraceFinder tf(refractionModel,attenuationModel);

	paths=tf.findPaths(src,trg,frequency/1.0e3,polarization,refl,requiredAccuracy);
	if(showLabels){
		if(!paths.empty()){
			std::cout << std::fixed
			<< "path length (m) "
			<< "path time (ns) "
			<< "launch angle "
			<< "recipt angle "
			<< "reflect angle "
			<< "miss dist. "
			<< "attenuation "
			<< "amplitude"
			<< std::endl;
		}
		else
			std::cout << "No solutions" << std::endl;
	}
	if(!dumpPaths){
		for(std::vector<RayTrace::TraceRecord>::const_iterator it=paths.begin(); it!=paths.end(); ++it){
			double signal = tf.signalStrength(*it,src,trg,refl);
			std::cout << std::left << std::fixed
			<< std::setprecision(2) << std::setw(15) << it->pathLen << ' '
			<< std::setprecision(2) << std::setw(14) << 1e9*it->pathTime << ' '
			<< std::setprecision(4) << std::setw(12) << it->launchAngle << ' '
			<< std::setprecision(4) << std::setw(12) << it->receiptAngle << ' '
			<< std::setprecision(3) << std::setw(13) << it->reflectionAngle << ' '
			<< std::setprecision(2) << std::setw(10) << it->miss << ' '
			<< std::scientific << std::setprecision(4) << std::setw(11) << it->attenuation << ' '
			//amplitude calculation, ignoring frequency response at both ends, angular response of receiver
			<< std::setw(10) << (it->attenuation*signal)
			<< std::endl;
		}
	}
	else{ //do write out path data
		pathPrinter<RayTrace::minimalRayPosition> print;
		for(std::vector<RayTrace::TraceRecord>::const_iterator it=paths.begin(); it!=paths.end(); ++it){
			tf.doTrace<RayTrace::minimalRayPosition>(src_z, it->launchAngle, RayTrace::rayTargetRecord(trg_z,sqrt((trg_x-src_x)*(trg_x-src_x)+(trg_y-src_y)*(trg_y-src_y))), refl, 0.0, 0.0, &print);
			std::cout << "\n\n";
		}
	}
	return(0);
}


// Copyright (c) 2018 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE ReduceEdgeCases
#include <boost/multi_array.hpp>
#include <boost/program_options.hpp>
#include <boost/test/unit_test.hpp>
#include <functional>
#include <iostream>
#include <limits>
#include <poplar/Engine.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <poplibs_test/Util.hpp>
#include <popops/Reduce.hpp>
#include <popops/codelets.hpp>
#include <poputil/TileMapping.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poputil;
using namespace popops;
using namespace poplibs_test::util;
using namespace poplibs_support;

const OptionFlags options;

BOOST_AUTO_TEST_CASE(Reduce_Nop_ADD_float) {

  // Tests for nop reductions, where the reduced dimension is 1, or
  // any of the input dimensions are 0.

  // Workaround GCC 5 bug.
  using TestCase =
      std::tuple<std::vector<std::size_t>,  // Input shape
                 std::vector<std::size_t>,  // Reduced dimensions
                 std::vector<std::size_t>>; // Expected output shape

  std::vector<TestCase> testCases = {
      TestCase{{2, 1, 2, 3}, {1, 2}, {2, 3}},
      TestCase{{2, 3, 4, 0}, {3}, {2, 3, 4}},
      TestCase{{2, 3, 4, 0}, {0}, {3, 4, 0}},
      TestCase{{1, 1, 1}, {1}, {1, 1}},
      TestCase{{1, 1, 1, 0}, {0, 1}, {1, 0}},
      TestCase{{0, 1, 2}, {}, {0, 1, 2}},
      TestCase{{0, 1, 2, 3}, {3}, {0, 1, 2}},
  };

  auto device = createTestDevice(TEST_TARGET, 1, 64);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  Sequence prog;
  for (const auto &testCase : testCases) {
    const auto &inShape = std::get<0>(testCase);
    const auto &dims = std::get<1>(testCase);
    const auto &outShape = std::get<2>(testCase);

    auto in = graph.addVariable(FLOAT, inShape, "in");
    poputil::mapTensorLinearly(graph, in);

    auto out =
        popops::reduce(graph, in, FLOAT, dims, popops::Operation::ADD, prog);
    BOOST_TEST(out.shape() == outShape);
  }
}

BOOST_AUTO_TEST_CASE(ReduceIntermediatePrec) {
  // Test that we can accumulate in higher precision by adding lots of small
  // values to a large value such that if it were done with half precision
  // accumulation all the smaller terms would be lost.
  auto tdevice = createTestDevice(TEST_TARGET);
  const auto &target = tdevice.getTarget();
  Graph graph(target);

  popops::addCodelets(graph);

  const auto N = 100;
  Tensor input = graph.addVariable(HALF, {N});
  poputil::mapTensorLinearly(graph, input);

  Sequence prog;

  auto out = reduce(graph, input, {0}, popops::Operation::ADD, prog);

  std::vector<float> hInput(N);
  hInput[0] = 8192;
  for (unsigned i = 1; i < N; ++i)
    hInput[i] = 1;

  graph.setInitialValue(input, poplar::ArrayRef<float>(hInput));
  graph.createHostRead("out", out);

  Engine engine(graph, prog, options);
  tdevice.bind([&](const Device &device) {
    engine.load(device);
    engine.run(0);

    std::vector<char> hVal(target.getTypeSize(HALF));
    float val;

    engine.readTensor("out", hVal.data(), hVal.data() + hVal.size());

    copyDeviceHalfToFloat(target, hVal.data(), &val, 1);

    // In the half precision range > 8192 the representation will round to
    // multiples of 8
    BOOST_CHECK_EQUAL(val, 8192 + ((N - 1) / 8) * 8);
  });
}

BOOST_AUTO_TEST_CASE(Reduce_Huge_ADD_float) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  // create a huge amount of partials and map them all to the same tile to blow
  // the vector list 12-bit count limit.
  auto in = graph.addVariable(HALF, {{3, 10500, 3}}, "in");
  graph.setTileMapping(in, 0);

  Sequence prog;
  popops::reduce(graph, in, HALF, {1}, popops::Operation::ADD, prog);

  // we expect this to throw an out of memory exception but NOT an exception
  // complaining about the number of partials.
  try {
    Engine e(graph, prog, {{"debug.allowOutOfMemory", "true"}});
  } catch (const poplar::graph_memory_allocation_error &) {
  };
}


#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <boost/bind.hpp>
using boost::cref;

#include "Cosmology.h"
#include "PowerSpectrum.h"
#include "Quadrature.h"
#include "BSPT.h"
#include "BSPTN.h"
#include "SPT.h"
#include "RegPT.h"

#include "Spline.h"
#include "SpecialFunctions.h"
#include "LinearPS.h"
#include "CMBc.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <sys/stat.h>
#include <math.h>       /* pow */
#include <boost/math/tools/roots.hpp>
#include <random>

#include <gsl/gsl_math.h>
#include <gsl/gsl_deriv.h>

// bottom limit of comoving distance integral
const double xim = 10.;

CMBc::CMBc(const Cosmology& C_, const PowerSpectrum& P_L_, real epsrel_)
: C(C_), P_L(P_L_)
{
    epsrel = epsrel_;
}

//////////////////////////////////////////
 /* Functions used in initialisation */
//////////////////////////////////////////

// Hubble function
double HAc(double omega0, double z){
	double omegaL= 1.-omega0;
	return  1./sqrt(omega0*pow(1.+z,3)+omegaL);}

// Functions for GM or SC formula
// neff(k)
  static double myfuncnw(double k,void * params){
    (void)(params);
    return log(neffnw(k));
  }

  static double myfuncehu(double k,void * params){
    (void)(params);
    return log(neffehu(k));
  }

  static double myfunclin(double k,void * params){
    (void)(params);
    return log(mylinearps(k));
  }

static double myneff(double k){
  gsl_function F;
  double result, abserr;
    F.params = 0;
    F.function = &myfuncnw;
    gsl_deriv_central(&F, k, 1e-6, &result, &abserr);
return k*result;
}

// Find K_nl
  double myknls(const PowerSpectrum& P_L)
  {
      std::mt19937 gen(2);
       const double lower_bound =  1e-4;
       const double upper_bound =  1000.;
      std::uniform_real_distribution<> dis(lower_bound, upper_bound);

      double pos_pt = dis(gen);
      double neg_pt = dis(gen);

      while ( (pow2(D_spt/dnorm_spt)*P_L(pos_pt)*pow3(pos_pt)/2./M_PI/M_PI  - 1.) < 0.){
          pos_pt = dis(gen);}

      while ( (pow2(D_spt/dnorm_spt)*P_L(neg_pt)*pow3(neg_pt)/2./M_PI/M_PI- 1.) > 0.){
          neg_pt = dis(gen);}

       const double about_zero_mag = 1e-5;
      for (;;)
      {
          double mid_pt = (pos_pt + neg_pt)/2.0;
          double f_mid_pt =  pow2(D_spt/dnorm_spt)*P_L(mid_pt)*pow3(mid_pt)/2./M_PI/M_PI -1.;

          if (fabs(f_mid_pt)  < about_zero_mag){
              return mid_pt;
                }

          if (f_mid_pt >= 0.){
              pos_pt = mid_pt;}
          else{
              neg_pt = mid_pt;}

      }
  }

// regpt damp term for post born prediction
static real regpt_exp(const PowerSpectrum& P_l, double q){
    return  P_l(q)/(6.*pow2(M_PI));
  }

// Calculate sigma_8 at some redshift
static double sigma8int(const PowerSpectrum& P_L, double k){
  return pow2(3.*((sin(k*8.) - k*8.*cos(k*8.))/pow2(k*8.))/k/8.) * pow2(k) * pow2(D_spt/dnorm_spt) * P_L(k)/2./M_PI/M_PI;
}

static double mysigma8(const PowerSpectrum& P_L){
  return sqrt(Integrate(bind(sigma8int,cref(P_L),_1),0.0001,30.,1e-4));
}

// Spline function z->comoving distance
Spline zofc;
// Spline linear growth factor + F2_dgp
Spline Dlin, Dlingr, F2DGP;
// Splines for HALOFIT
Spline phpars0,phpars1,phpars2,phpars3,phpars4,phpars5,phpars6,phpars7,phpars8,phpars9,phpars10,phpars11,phpars12;
// Splines for dgp 1-loop matter power Spectrum
Spline fdgps, cdgps, idgps, kldgps, p22spl,p13spl, damp_termcmb;
// Comoving distance to z=1000 (~CMB)
double xis;
// comoving distance to z=40 as in TN's computation
double xis40;

// Splines for GM formula
Spline myneffspl, mys8spl, myknlspl;

// Initialize z(comoving_dist) and xi_star = comoving distance to CMB
// a = 1 linear
// a = 2 1-loop (+ initialises regpt components)
// a = 3 Gil-Marin or Scoccimaro [+ initialises all halofit params, n_eff(k), k_nl(z) and sigma_8(z)]
 void CMBc::comdist_init(int a, double omega0, double mg1){
  SPT spt(C, P_L, 1e-3);
  RegPT rpt(C, P_L, 1e-3);
  BSPT bspt(C, P_L, 1e-3);
  IOW iow;

  // initialise damping factor for regpt
  if(a==2){
  rpt.sigmad_init();
    }

   // initialise EHU PS and Fonseca NW
    if(a==3){
      bspt.mypspline(2);
    }

  double h0inv = 2997.92;
  const double zmin = 1e-4;
  const double zmax = 1100.;
  const double kmin = 1e-4;
  const double kmax = 100.;

  vector<double> ztable, ktable, ctable, dtable, dgrtable, f2table;
  // Halofit tables
  vector<double> phtable0,phtable1,phtable2,phtable3,phtable4,phtable5,phtable6,phtable7,phtable8,phtable9,phtable10,phtable11,phtable12;
  // 1-LOOP SPT nDGP GROWTH TABLES
  vector<double> ftab,ctab,itab,kltab;
  // 1-Loop k-components
  vector<double> p13tab,p22tab, dampt;
  // GM formula tables
  vector<double> nefftab, knltab, sig8tab;

  xis =  h0inv*Integrate(bind(HAc,omega0,_1),0.,zmax,1e-4);
  xis40 = h0inv*Integrate(bind(HAc,omega0,_1),0.,40.,1e-4);

  const int n1 = 1000;
  double cval,zval,scalef,kval,sigd;

  for(int i = 0; i<n1; i++ ){
    zval = zmin*exp(i*log(zmax/zmin)/(n1-1.));
    kval = kmin*exp(i*log(kmax/kmin)/(n1-1.));

    scalef = 1./(1.+zval);
    iow.inite(scalef,omega0,mg1,1.,1.);

    cval = h0inv*Integrate(bind(HAc,omega0,_1),zmin,zval,1e-4);

    ztable.push_back(zval);
    ktable.push_back(kval);
    ctable.push_back(cval);

    dtable.push_back(D_spt/dnorm_spt);
    dgrtable.push_back(Dl_spt/dnorm_spt);
    f2table.push_back(F_spt/pow2(dnorm_spt));

    if (a==2) {
        p22tab.push_back(pow4(dnorm_spt/Dl_spt)*spt.P22_dd(kval));
        p13tab.push_back(pow4(dnorm_spt/Dl_spt)*spt.P13_dd(kval));
        sigd = pow2(kval)*Integrate<ExpSub>(bind(regpt_exp, cref(P_L), _1), kmin, kval/2., 1e-4);
        dampt.push_back(sigd);
    }

    if (a ==3 ) {
      spt.phinit(scalef,omega0);
      phtable0.push_back(phpars[0]);
      phtable1.push_back(phpars[1]);
      phtable2.push_back(phpars[2]);
      phtable3.push_back(phpars[3]);
      phtable4.push_back(phpars[4]);
      phtable5.push_back(phpars[5]);
      phtable6.push_back(phpars[6]);
      phtable7.push_back(phpars[7]);
      phtable8.push_back(phpars[8]);
      phtable9.push_back(phpars[9]);
      phtable10.push_back(phpars[10]);
      phtable11.push_back(phpars[11]);
      phtable12.push_back(phpars[12]);

        if (i==0) {
          knltab.push_back(myknls(cref(P_L)));
          }
        else if( knltab[i-1] > 200. ){
           knltab.push_back(knltab[i-1]);
        }
        else{
           knltab.push_back(myknls(cref(P_L)));
              }
             printf("%d %e \n", i, knltab[i]);

      sig8tab.push_back(mysigma8(cref(P_L)));
      nefftab.push_back(myneff(kval));

    }
      }

      zofc = LinearSpline(ctable,ztable);
      Dlin = LinearSpline(ztable,dtable);
      Dlingr = LinearSpline(ztable,dgrtable);
      F2DGP = LinearSpline(ztable,f2table);

      if (a==2) {
        p22spl=LinearSpline(ktable,p22tab);
        p13spl=LinearSpline(ktable,p13tab);
        damp_termcmb = LinearSpline(ktable,dampt);
            }
      if (a==3) {
        phpars0 = LinearSpline(ztable,phtable0);
        phpars1 = LinearSpline(ztable,phtable1);
        phpars2 = LinearSpline(ztable,phtable2);
        phpars3 = LinearSpline(ztable,phtable3);
        phpars4 = LinearSpline(ztable,phtable4);
        phpars5 = LinearSpline(ztable,phtable5);
        phpars6 = LinearSpline(ztable,phtable6);
        phpars7 = LinearSpline(ztable,phtable7);
        phpars8 = LinearSpline(ztable,phtable8);
        phpars9 = LinearSpline(ztable,phtable9);
        phpars10 = LinearSpline(ztable,phtable10);
        phpars11 = LinearSpline(ztable,phtable11);
        phpars12 = LinearSpline(ztable,phtable12);

        myknlspl = LinearSpline(ztable,knltab);
        mys8spl = LinearSpline(ztable,sig8tab);
        myneffspl = LinearSpline(ktable,nefftab);

      }
}

// B_LSS integrands
// tree analytic
static double btlssa_integrand(const Cosmology& C, const PowerSpectrum& P_L, int a, double params[], double l1, double l2, double x, double xi ){
   BSPT bspt(C, P_L, 1e-4);
   IOW iow;
   double bis, kernel, F2A, F2B, F2C, growth;
   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);
   double k1 = l1/xi;
   double k2 = l2/xi;

   D_spt = Dlin(myz) * dnorm_spt;
   Dl_spt = Dlin(myz) * dnorm_spt; //Dlingr(myz) * dnorm_spt;
   F_spt = F2DGP(myz) * dnorm_spt;
   bis = bspt.Btree(a, k1, k2, x);

   return prefactor * bis * pow2(xi);
}

// 1-loop analytic
static double bllssa_integrand(const Cosmology& C,const PowerSpectrum& P_L, int a, double params[], double l1, double l2, double x, double xi ){
   BSPT bspt(C, P_L, 1e-2);
   IOW iow;
   double bis, kernel;
   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);
   switch (a) {
     case 1:
      Dl_spt = Dlin(myz) * dnorm_spt;
      break;
      case 2:
   iow.inite2(scalef,params[0],params[1],params[2],params[3]);
      break;
 }
   double k1 = l1/xi;
   double k2 = l2/xi;
   bis = bspt.Bloop(a, k1, k2, x);
   return prefactor * bis * pow2(xi);
}

// Gil-Marin formula
static double bgmlssa_integrand(const Cosmology& C,const PowerSpectrum& P_L,  double params[], double l1, double l2, double x, double xi ){
   SPT spt(C, P_L, 1e-3);
   double vars[6],F2A,F2B,F2C,p1,p2,p3;

   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);

   double k1 = l1/xi;
   double k2 = l2/xi;
   double k3 = sqrt(pow2(k1) + pow2(k2) + 2.*k2*k1*x);

    D_spt =  Dlin(myz) * dnorm_spt;
    F_spt =  F2DGP(myz) * pow2(dnorm_spt);
    vars[0] = mys8spl(myz);
    vars[1] = myknlspl(myz);
    vars[2] = 1.;
    vars[3] = (1.- F_spt * 7./2./pow2(D_spt));

     vars[4] = myneffspl(k1);
     vars[5] = myneffspl(k2);
     F2A = F2fit(vars,k1,k2,x);

     vars[4] = myneffspl(k1);
     vars[5] = myneffspl(k3);
     F2B = F2fit(vars,k1,k3,-(k2*x+k1)/k3);

     vars[4] = myneffspl(k2);
     vars[5] = myneffspl(k3);
     F2C = F2fit(vars,k2,k3,-(k1*x+k2)/k3);

    phpars[0] = phpars0(myz);
    phpars[1] = phpars1(myz);
    phpars[2] = phpars2(myz);
    phpars[3] = phpars3(myz);
    phpars[4] = phpars4(myz);
    phpars[5] = phpars5(myz);
    phpars[6] = phpars6(myz);
    phpars[7] = phpars7(myz);
    phpars[8] = phpars8(myz);
    phpars[9] = phpars9(myz);
    phpars[10] = phpars10(myz);
    phpars[11] = phpars11(myz);
    phpars[12] = phpars12(myz);

     if (k1>100.) {
      p1 = spt.PHALO(100.);
     }
     else{
     p1 = spt.PHALO(k1);
        }
        if (k2>100.) {
          p2 = spt.PHALO(100.);
        }
        else{
        p2 = spt.PHALO(k2);
           }

           if (k3>100.) {
              p3 = spt.PHALO(100.);
           }
           else{
           p3 = spt.PHALO(k3);
              }

    double bis =2.*(p1*p2*F2A + p3*p1*F2B + p2*p3*F2C);

    return prefactor * bis * pow2(xi);
}

// SC fitting formula
static double bsclssa_integrand(const Cosmology& C,const PowerSpectrum& P_L,  double params[], double l1, double l2, double x, double xi ){
   SPT spt(C, P_L, 1e-3);
   double vars[6],F2A,F2B,F2C,p1,p2,p3;

   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);

   double k1 = l1/xi;
   double k2 = l2/xi;
   double k3 = sqrt(pow2(k1) + pow2(k2) + 2.*k2*k1*x);

    D_spt =  Dlin(myz) * dnorm_spt;
    F_spt =  F2DGP(myz) * pow2(dnorm_spt);
    vars[0] = mys8spl(myz);
    vars[1] = myknlspl(myz);
    vars[2] = 1.;
    vars[3] = (1.- F_spt * 7./2./pow2(D_spt));

     vars[4] = myneffspl(k1);
     vars[5] = myneffspl(k2);
     F2A = F2fitsc(vars,k1,k2,x);

     vars[4] = myneffspl(k1);
     vars[5] = myneffspl(k3);
     F2B = F2fitsc(vars,k1,k3,-(k2*x+k1)/k3);

     vars[4] = myneffspl(k2);
     vars[5] = myneffspl(k3);
     F2C = F2fitsc(vars,k2,k3,-(k1*x+k2)/k3);

    phpars[0] = phpars0(myz);
    phpars[1] = phpars1(myz);
    phpars[2] = phpars2(myz);
    phpars[3] = phpars3(myz);
    phpars[4] = phpars4(myz);
    phpars[5] = phpars5(myz);
    phpars[6] = phpars6(myz);
    phpars[7] = phpars7(myz);
    phpars[8] = phpars8(myz);
    phpars[9] = phpars9(myz);
    phpars[10] = phpars10(myz);
    phpars[11] = phpars11(myz);
    phpars[12] = phpars12(myz);

     if (k1>100.) {
      p1 = spt.PHALO(100.);
     }
     else{
     p1 = spt.PHALO(k1);
        }
        if (k2>100.) {
          p2 = spt.PHALO(100.);
        }
        else{
        p2 = spt.PHALO(k2);
           }

           if (k3>100.) {
              p3 = spt.PHALO(100.);
           }
           else{
           p3 = spt.PHALO(k3);
              }

    double bis =2.*(p1*p2*F2A + p3*p1*F2B + p2*p3*F2C);

    return prefactor * bis * pow2(xi);
}

// Numerical tree level
static double btnlssa_integrand(const Cosmology& C, const PowerSpectrum& P_L, int a, double params[], double l1, double l2, double x, double xi ){
   BSPT bspt(C, P_L, 1e-3);
   IOW iow;
   double bis, kernel, F2A, F2B, F2C, growth;
   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);
   double k1 = l1/xi;
   double k2 = l2/xi;
   double k3 = sqrt(pow2(k1) + pow2(k2) + 2.*k2*k1*x);

   double vars[6];
   vars[0] = params[0];
   vars[1] = params[1];
   vars[2] = params[2];
   vars[3] = params[3];
   vars[4] = scalef;
   vars[5] = 1.*a; // doubling as choice of dgp (1), ide (2) , fr(3)

   bis = bspt.Btreen(vars, k1, k2, x);
   return prefactor * bis * pow2(xi);
}

// numerical 1-loop level
static double blnlssa_integrand(const Cosmology& C, const PowerSpectrum& P_L, int a, double params[], double l1, double l2, double x, double xi ){
   BSPT bspt(C, P_L, 1e-4);
   IOW iow;
   double bis, kernel, F2A, F2B, F2C, growth;
   double h0 = 1./2997.92;
   double lensingk = (xis-xi)/xi/xis;
   double myz = zofc(xi);
   double scalef = 1./(1.+myz);
   double prefactor = pow3(3.*params[0]*pow2(h0)/(2.*scalef) * lensingk);
   double k1 = l1/xi;
   double k2 = l2/xi;
   double vars[9];
   vars[0] = params[0];
   vars[1] = params[1];
   vars[2] = params[2];
   vars[3] = params[3];
   vars[4] = scalef;
   vars[5] = 1.*a; // doubling as choice of dgp (1), ide (2) , fr(3)
   vars[6] = params[4];
   vars[7] = params[5];
   vars[8] = params[6];
   bis = bspt.Bloopn(vars, k1, k2, x);
   return prefactor * bis * pow2(xi);
}

// POST BORN STUFF

// C_l(xi1, xi2) integrands
// linear
static double cl_integrandt(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l2, double xi1, double xi2){
  double h0 = 1./2997.92;
  double k2 = l2/xi2;
  double lenker1 = (xi1 - xi2)/xi2/xi1 ;
  double lenker2 = (xis - xi2)/xi2/xis;
  double weylpot2 =  pow2(Dlin(zofc(xi2))) * P_L(k2) * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi2)) / 2. / pow2(k2));

  return  lenker1 * lenker2 / pow2(xi2) * weylpot2 ;
}

// 1-loop (regpt)
static double cl_integrandl(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l2, double xi1, double xi2){
  double D0, D0sqr, D2, expon, p13, pl,  ploop,ploopr,h0,k2,weylpot2,lenker1,lenker2;
   D0 =  pow2(Dlin(zofc(xi2)));
   h0 = 1./2997.92;
   k2 = l2/xi2;

  expon = D0 * damp_termcmb(k2) ;
  if(expon >= 80.){
   return 0.;
  }
  else{

   lenker1 = (xi1 - xi2)/xi2/xi1;
   lenker2 = (xis - xi2)/xi2/xis;

  D0sqr = pow2(D0);
  pl = D0 * P_L(k2);
  p13 =  D0sqr*p13spl(k2);
  ploop = pl+ D0sqr*p22spl(k2) + p13;
  D2 = pow2(p13/pl/2.)*D0;

  ploopr = exp(-expon)*(ploop + expon/2.*(2.*pl + p13) + pow2(expon)/4.*pl + P_L(k2)*D2);

  weylpot2 =   ploopr * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi2)) / 2. / pow2(k2));

  return  lenker1 * lenker2 / pow2(xi2) * weylpot2 ;
}
}

// 1-loop (spt)
static double cl_integrandspt(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l2, double xi1, double xi2){
  double D0, D0sqr, D2, expon, p13, pl,  ploop,ploopr,h0,k2,weylpot2,lenker1,lenker2;
   D0 =  pow2(Dlin(zofc(xi2)));
   h0 = 1./2997.92;
   k2 = l2/xi2;
   lenker1 = (xi1 - xi2)/xi2/xi1;
   lenker2 = (xis - xi2)/xi2/xis;

  D0sqr = pow2(D0);
  pl = D0 * P_L(k2);
  p13 =  D0sqr*p13spl(k2);
  ploop = pl+ D0sqr*p22spl(k2) + p13;

  weylpot2 =   ploop * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi2)) / 2. / pow2(k2));

  return  lenker1 * lenker2 / pow2(xi2) * weylpot2 ;
}

// halofit
static double cl_integrandh(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l2, double xi1, double xi2){
  SPT spt(C, P_L, 1e-3);

  double h0 = 1./2997.92;
  double k2 = l2/xi2;
  double lenker1 = (xi1 - xi2)/xi2/xi1 ;
  double lenker2 = (xis - xi2)/xi2/xis;
  double myz = zofc(xi2);

    D_spt =  Dlin(myz) * dnorm_spt;
    phpars[0] = phpars0(myz);
    phpars[1] = phpars1(myz);
    phpars[2] = phpars2(myz);
    phpars[3] = phpars3(myz);
    phpars[4] = phpars4(myz);
    phpars[5] = phpars5(myz);
    phpars[6] = phpars6(myz);
    phpars[7] = phpars7(myz);
    phpars[8] = phpars8(myz);
    phpars[9] = phpars9(myz);
    phpars[10] = phpars10(myz);
    phpars[11] = phpars11(myz);
    phpars[12] = phpars12(myz);

  double weylpot2 =   spt.PHALO(k2) * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi2)) / 2. / pow2(k2));

  return  lenker1 * lenker2 / pow2(xi2) * weylpot2 ;
}

// M(l1,l2) function integrands
// tree level
static double mfunc_integrandt(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l1, double l2, double xi1){
  double h0 = 1./2997.92;
  double mycl = pow4(l2)*Integrate(bind(cl_integrandt,cref(C),cref(P_L), omega0, l2, xi1, _1), xim,xi1,1e-4);  // C_l2 (xi1, xis)
  double k1 = l1/xi1;
  double weylpot1 =  pow2(Dlin(zofc(xi1))) * P_L(k1) * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi1)) / 2. / pow2(k1));

  return pow2((xis - xi1)/pow2(xi1)/xis) * weylpot1 * mycl;
}

// 1-loop level (RegPT implementation)
static double mfunc_integrandl(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l1, double l2, double xi1){
  double D0,D0sqr, D2, expon, p13, pl,  ploop,ploopr,h0,mycl,k1,weylpot1;
  h0 = 1./2997.92;
  D0 =  pow2(Dlin(zofc(xi1)));
  k1 = l1/xi1;
  expon = D0 * damp_termcmb(k1) ;
  if(expon >= 80. ){
   return 0.;
  }
  else{
     mycl = pow4(l2) * Integrate(bind(cl_integrandl, cref(C), cref(P_L), omega0, l2, xi1, _1), xim,xi1,1e-2);   // C_l2 (xi1, xis)
     D0sqr = pow2(D0);
     pl = D0 * P_L(k1);
     p13 =  D0sqr*p13spl(k1);
     ploop = pl+ D0sqr*p22spl(k1) + p13;
     D2 = pow2(p13/pl/2.)*D0;

     ploopr = exp(-expon)*(ploop + expon/2.*(2.*pl + p13) + pow2(expon)/4.*pl + P_L(k1)*D2);

     weylpot1 =  ploopr * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi1)) / 2. / pow2(k1));

  return pow2( (xis - xi1)/pow2(xi1)/xis ) * weylpot1 * mycl;
}
}

// pure spt
static double mfunc_integrandspt(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l1, double l2, double xi1){
  double D0,D0sqr, D2, expon, p13, pl,  ploop,ploopr,h0,mycl,k1,weylpot1;
  h0 = 1./2997.92;
  D0 =  pow2(Dlin(zofc(xi1)));
  k1 = l1/xi1;

     mycl = pow4(l2) * Integrate(bind(cl_integrandspt, cref(C), cref(P_L), omega0, l2, xi1, _1), xim,xi1,1e-2);   // C_l2 (xi1, xis)
     D0sqr = pow2(D0);
     pl = D0 * P_L(k1);
     p13 =  D0sqr*p13spl(k1);
     ploop = pl+ D0sqr*p22spl(k1) + p13;

     weylpot1 =  ploop * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi1)) / 2. / pow2(k1));

  return pow2( (xis - xi1)/pow2(xi1)/xis ) * weylpot1 * mycl;
}

// halofit
static double mfunc_integrandh(const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l1, double l2, double xi1){
  SPT spt(C, P_L, 1e-2);

  double h0 = 1./2997.92;
  double mycl = pow4(l2)*Integrate(bind(cl_integrandh,cref(C), cref(P_L), omega0, l2, xi1, _1), xim,xi1,1e-4);   // C_l2 (xi1, xis)
  double k1 = l1/xi1;
  double myz = zofc(xi1);

    D_spt =  Dlin(myz) * dnorm_spt;
    phpars[0] = phpars0(myz);
    phpars[1] = phpars1(myz);
    phpars[2] = phpars2(myz);
    phpars[3] = phpars3(myz);
    phpars[4] = phpars4(myz);
    phpars[5] = phpars5(myz);
    phpars[6] = phpars6(myz);
    phpars[7] = phpars7(myz);
    phpars[8] = phpars8(myz);
    phpars[9] = phpars9(myz);
    phpars[10] = phpars10(myz);
    phpars[11] = phpars11(myz);
    phpars[12] = phpars12(myz);

  double weylpot1 =  spt.PHALO(k1) * pow2(3. * omega0 * pow2(h0) * (1.+zofc(xi1)) / 2. / pow2(k1));

  return pow2( (xis - xi1)/pow2(xi1)/xis ) * weylpot1 * mycl;
}

// M(l1,l2) as in Eq.4.5 of 1605.05662
static double Mfunc(int a, const Cosmology& C, const PowerSpectrum& P_L, double omega0, double l1, double l2){
    switch (a) {
      case 1:
      return pow4(l1) * Integrate(bind(mfunc_integrandt,cref(C),cref(P_L), omega0, l1, l2, _1), xim,xis40,1e-3); // tree level
      break;
      case 2:
      return pow4(l1) * Integrate(bind(mfunc_integrandl,cref(C),cref(P_L), omega0, l1, l2, _1), xim,xis40,1e-3); // 1-loop (regpt)
      break;
      case 3:
      return pow4(l1) * Integrate(bind(mfunc_integrandh,cref(C),cref(P_L), omega0, l1, l2, _1), xim,xis,1e-3); // halofit
      break;
      case 4:
      return pow4(l1) * Integrate(bind(mfunc_integrandspt,cref(C),cref(P_L), omega0, l1, l2, _1), xim,xis40,1e-3); // 1-loop (spt)
      break;
}}

// B_LSS contribution
// a chooses LCDM (1) or nDGP/MG analytic (2) (tree or 1-loop only) or 3(IDE) or 4 [f(R)] (only numerical)
// b chooses tree analytic (1) or 1-loop analytic (2) or Gil-Marin (3) or Scoccimaro (4) or tree numerical (5) or 1-loop numerical (6)
// params[0] = omega0, params[1]=mg1, params[2]=mg2, params[3] =mg3

// LSS bispectrum
double CMBc::BLSS(int a, int b,  double params[], double l1, double l2, double x) const{
switch (b) {
  case 1:
   return   Integrate(bind(btlssa_integrand,cref(C),cref(P_L), a, params, l1, l2, x, _1),xim,xis,epsrel); // tree level EdS
   break;
  case 2:
    return  Integrate(bind(bllssa_integrand,cref(C),cref(P_L),a, params, l1, l2, x, _1),xim,xis40,epsrel); // 1-loop EdS
    break;
  case 3:
    return  Integrate(bind(bgmlssa_integrand,cref(C),cref(P_L), params, l1, l2, x, _1),xim,xis,epsrel); // Gil-Marin fit
    break;
  case 4:
    return  Integrate(bind(bsclssa_integrand,cref(C),cref(P_L), params, l1, l2, x, _1),xim,xis40,epsrel); // Scoccimaro fit
    break;
  case 5:
  if (a==1) {
    return  Integrate(bind(btlssa_integrand,cref(C),cref(P_L), a, params, l1, l2, x, _1),xim,xis40,epsrel); // For GR we use EdS
    }
  else{
    return  Integrate(bind(btnlssa_integrand,cref(C),cref(P_L),a-1, params, l1, l2, x, _1),xim,xis40,epsrel); // tree level numerical
  }
  break;
  case 6:
  if (a==1) {
    return  Integrate(bind(bllssa_integrand,cref(C),cref(P_L), a, params, l1, l2, x, _1),xim,xis40,epsrel);  // For GR we use EdS
    }
  else{
    return  Integrate(bind(blnlssa_integrand,cref(C),cref(P_L),a-1, params, l1, l2, x, _1),xim,xis40,epsrel); // 1-loop level numerical
  }
    break;
  }
}

// BLSS lensing kernel
double CMBc::BLSS_kernel(int a, int b, double params[], double l1, double l2, double x, double xi) const{
  switch (b) {
    case 1:
     return   btlssa_integrand(cref(C),cref(P_L), a, params, l1, l2, x, xi); // tree level EdS
     break;
    case 2:
      return   bllssa_integrand(cref(C),cref(P_L), a, params, l1, l2, x, xi); // 1-loop EdS
      break;
    case 3:
      return   bgmlssa_integrand(cref(C),cref(P_L), params, l1, l2, x, xi); // Gil-Marin fit
      break;
    }
}

// post born correction
double CMBc::BPB(int a, double omega0, double l1, double l2, double x) const{
   double l1sqr = pow2(l1);
   double l2sqr = pow2(l2);
   double l3 = sqrt(l1sqr + l2sqr + 2.*l1*l2*x);
   double l3sqr = pow2(l3);
   double l1l3 = -(l1sqr + l1*l2*x);
   double l2l3 = -(l2sqr + l1*l2*x);
   return   2.*x/l1/l2 * (l1l3 * Mfunc(a, cref(C), cref(P_L), omega0, l1,l2) + l2l3 * Mfunc(a,cref(C), cref(P_L), omega0, l2,l1))
           +2.*l2l3/l2sqr/l3sqr * (l2*l1*x * Mfunc(a,cref(C), cref(P_L), omega0, l2,l3) + l1l3 * Mfunc(a,cref(C), cref(P_L), omega0, l3,l2))
           +2.*l1l3/l3sqr/l1sqr * (l2l3 * Mfunc(a,cref(C), cref(P_L), omega0, l3,l1) + l2*l1*x * Mfunc(a,cref(C), cref(P_L), omega0, l1,l3));
    }

// CMB lensing spectrum
// nDGP or LCDM are chosen via comdist_init(int a, double omega0, double mg1) function (Only linear growth dependence - 1-loop uses UsA + EdS)
double CMBc::CMBla(int a, int b, double params[],double l1, double l2, double x) const{
  double l1sqr = pow2(l1);
  double l2sqr = pow2(l2);
  double l3 = sqrt(l1sqr + l2sqr + 2.*l1*l2*x);
  int lw1,lw2,lw3;
  lw1 = (int)l1;
  lw2 = (int)l2;
  lw3 = (int)round(l3);
  return  wigner3j(lw1, lw2, lw3) * sqrt((2.*l1+1.)*(2.*l2+1.)*(2.*l3+1.)/4./M_PI) * (BLSS(a,b, params, l1,l2,x) + BPB(a, params[0], l1,l2,x));
}


#include "board.h"
#include "count_reader.h"
#include <fstream>
#include <chrono>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

/*
  0 - unknown
  1 - (black turn) black cannot move (brown win)
  2 - (brown turn) brown has move to state 1
 2*n - (brown turn) brown has move to state 2*n - 1
 2*n + 1 - (black turn) all black move lead to state 2*m (m <= n)
 */

void usage(char *command) {
  std::cerr << "Usage : " << command << " n count" << std::endl;
  std::cerr << " n - value in the table" << std::endl;
  std::cerr << " [count] - how many samples to show (zero for all) " << std::endl;
  exit(0);
}

template<int SIZE, int capture_type>
void show_board(CountReader<SIZE, capture_type> &cr, Board<SIZE> const& b) {
  // const size_t table_size = (1ull << SIZE) * (capture_type > 1 ? SIZE : Board<SIZE>::HSIZE());
  std::cerr << "b.v=" << b.v << "\n" << b << std::endl;
  Board<SIZE> b1 = b;
  if (b.v >= CountReader<SIZE, capture_type>::table_size()) b1 = b.flip();
  // std::cerr << "b1.v=" << b1.v << "\n" << b1 << std::endl;
  int c = cr.get(b1.v);
  std::cerr << c << std::endl;
  if (c <= 1) return;
  std::cerr << "----" << std::endl;
  auto ns = b.next_states();
  Board<SIZE> best_n = b;
  int loss_count = 0;
  int ns_size = 0;
  for (auto n : ns) {
    // std::cerr << "n=" << n << ",cr.get(n)=" << int(cr.get(n)) << std::endl;
    ns_size++;
    Board<SIZE> n1(n);
    // std::cerr << "n1=" << n1 << std::endl;
    // if (n1.v >= table_size) n1 = n1.flip();
    if (cr.get(n1.v) == 0) loss_count++;
    if (cr.get(n1.v) == c - 1) {
      best_n = Board<SIZE>(n);
    }
  }
  std::cerr << "lost / all = " << loss_count << " / " << ns_size << std::endl;
  if (!(best_n == b))
    show_board<SIZE, capture_type>(cr, best_n);
}

template<int SIZE, int capture_type>
void show() {
  const int SSIZE = Board<SIZE>::WIDTH() * Board<SIZE>::HEIGHT() + 1;
  CountReader<SIZE, capture_type> cr;
  std::string s_all;
  std::string s;
  std::cerr << "Ready" << std::endl;
  while (std::cin >> s) {
    s_all += s;
    if (s_all.size() >= SSIZE) {
      Board<SIZE> b(s_all.substr(0, SSIZE));
      show_board<SIZE, capture_type>(cr,b);
      s_all = s_all.substr(SSIZE, s_all.size());
    }
  }
}

int main(int ac, char **ag) {
  int board_size;
  int capture_type;

  po::options_description options("all_options");
  options.add_options()
    ("board-size,n",
     po::value<int>(&board_size)->default_value(25),
     "board size (25, 31, 33)")
    ("capture-type,t",
     po::value<int>(&capture_type)->default_value(0),
     "capture type : 0 (ALL), 1(any corner), 2(one corner), 3(1,0), 4(2,0)")
    ;
  po::variables_map vm;
  try
    {
      po::store(po::parse_command_line(ac, ag, options), vm);
      po::notify(vm);
    }
  catch (std::exception& e)
    {
      std::cerr << "error in parsing options" << std::endl
		<< e.what() << std::endl;
      std::cerr << options << std::endl;
      return 1;
    }
  if (vm.count("help")) {
    std::cerr << options << std::endl;
    return 0;
  }
  if (board_size == 25) {
    if (capture_type == 0)
      show<25, 0>();
    else if (capture_type == 1)
      show<25, 1>();
    else if (capture_type == 2)
      show<25, 2>();
    else if (capture_type == 3)
      show<25, 3>();
    else if (capture_type == 4)
      show<25, 4>();
    else {
      std::cerr << options << std::endl;
      return 0;
    }
  }
  else if (board_size == 31) {
    if (capture_type == 0)
      show<31, 0>();
  }
  else if (board_size == 33) {
    if (capture_type == 0)
      show<33, 0>();
  }
  else {
    std::cerr << options << std::endl;
    return 0;
  }

}


//
//  Scheduler.cpp
//
//
//  Created by Ben Lengerich on 1/27/16.
//
//

#include "Scheduler.hpp"

#include <Eigen/Dense>
#include <stdexcept>
#include <iostream>
#include <memory>
#include <mutex>
#include <node.h>
#include <stdio.h>
#include <thread>
#include <typeinfo>
#include <unistd.h>
#include <unordered_map>
#include <uv.h>

#ifdef BAZEL
#include "Algorithms/Algorithm.hpp"
#include "Algorithms/AlgorithmOptions.hpp"
#include "Algorithms/BrentSearch.hpp"
#include "Algorithms/GridSearch.hpp"
#include "Algorithms/IterativeUpdate.hpp"
#include "Algorithms/ProximalGradientDescent.hpp"
#include "Algorithms/HypoTestPlaceHolder.h"
#include "Models/AdaMultiLasso.hpp"
#include "Models/GFlasso.h"
#include "Models/lasso.hpp"
#include "Models/LinearRegression.hpp"
#include "Models/Model.hpp"
#include "Models/ModelOptions.hpp"
#include "Models/MultiPopLasso.hpp"
#include "Models/TreeLasso.hpp"
#include "Models/LinearMixedModel.hpp"
#include "Models/SparseLMM.h"
#include "Stats/FisherTest.h"
#include "Stats/Chi2Test.h"
#include "Stats/WaldTest.h"
#include "Graph/NeighborSelection.hpp"
#include "Graph/GraphicalLasso.hpp"
#include "Scheduler/Job.hpp"
#else
#include "../Algorithms/Algorithm.hpp"
#include "../Algorithms/AlgorithmOptions.hpp"
#include "../Algorithms/BrentSearch.hpp"
#include "../Algorithms/GridSearch.hpp"
#include "../Algorithms/IterativeUpdate.hpp"
#include "../Algorithms/ProximalGradientDescent.hpp"
#include "../Algorithms/HypoTestPlaceHolder.h"
#include "../Models/AdaMultiLasso.hpp"
#include "../Models/GFlasso.h"
#include "../Models/lasso.hpp"
#include "../Models/LinearRegression.hpp"
#include "../Models/Model.hpp"
#include "../Models/ModelOptions.hpp"
#include "../Models/MultiPopLasso.hpp"
#include "../Models/LinearMixedModel.hpp"
#include "../Models/SparseLMM.h"
#include "../Models/TreeLasso.hpp"
#include "../Stats/FisherTest.h"
#include "../Stats/Chi2Test.h"
#include "../Stats/WaldTest.h"
#include "../Graph/NeighborSelection.hpp"
#include "../Graph/GraphicalLasso.hpp"
#include "../Scheduler/Job.hpp"
#endif

using namespace std;

//////////////////////////////////////////
// Constructors
//////////////////////////////////////////

Scheduler::Scheduler()
: next_algorithm_id(1)
, next_model_id(1)
, next_job_id(1) {
	algorithms_map = std::unordered_map<algorithm_id_t, shared_ptr<Algorithm>>();
	models_map = std::unordered_map<model_id_t, shared_ptr<Model>>();
	jobs_map = std::unordered_map<job_id_t, shared_ptr<Job_t>>();
}

Scheduler& Scheduler::operator=(Scheduler const& s) {
    return Scheduler::Instance();
}

Scheduler& Scheduler::Instance() {
	static Scheduler s_instance;
	return s_instance;
}

/////////////////////////////////////////////////////////
// Public Functions
/////////////////////////////////////////////////////////

algorithm_id_t Scheduler::newAlgorithm(const AlgorithmOptions_t& options) {
	const algorithm_id_t id = getNewAlgorithmId();
	// Determine the type of algorithm to create.
	if (ValidAlgorithmId(id)) {
		switch(options.type) {
			case algorithm_type::brent_search:
				algorithms_map[id] = shared_ptr<BrentSearch>(new BrentSearch(options.options));
				break;
			case algorithm_type::grid_search:
				algorithms_map[id] = shared_ptr<GridSearch>(new GridSearch(options.options));
				break;
			case algorithm_type::iterative_update:
				algorithms_map[id] = shared_ptr<IterativeUpdate>(new IterativeUpdate(options.options));
				break;
			case algorithm_type::proximal_gradient_descent:
				algorithms_map[id] = shared_ptr<ProximalGradientDescent>(new ProximalGradientDescent(options.options));
				break;
			case algorithm_type::hypo_test:
				algorithms_map[id] = shared_ptr<HypoTestPlaceHolder>(new HypoTestPlaceHolder(options.options));
				break;
			case algorithm_type::neighbor_selection:
				algorithms_map[id] = shared_ptr<NeighborSelection>(new NeighborSelection(options.options));
				break;
            case algorithm_type::graphical_lasso:
				algorithms_map[id] = shared_ptr<GraphicalLasso>(new GraphicalLasso(options.options));
				break;
			default:
				return 0;
		}
	}

	return id;
}

model_id_t Scheduler::newModel(const ModelOptions_t& options) {
	const model_id_t id = getNewModelId();
	if (ValidModelId(id)) {
		switch(options.type) {
			case ada_multi_lasso: {
				models_map[id] = shared_ptr<AdaMultiLasso>(new AdaMultiLasso(options.options));
				break;
			}
			case gf_lasso: {
				models_map[id] = shared_ptr<Gflasso>(new Gflasso(options.options));
				break;
			}
			case linear_regression: {
				models_map[id] = shared_ptr<LinearRegression>(new LinearRegression(options.options));
				break;
			}
			case multi_pop_lasso: {
				models_map[id] = shared_ptr<MultiPopLasso>(new MultiPopLasso(options.options));
				break;
			}
			case tree_lasso: {
				models_map[id] = shared_ptr<TreeLasso>(new TreeLasso(options.options));
				break;
			}
			case fisher_test: {
				models_map[id] = shared_ptr<FisherTest>(new FisherTest(options.options));
				break;
			}
			case chi2_test: {
				models_map[id] = shared_ptr<Chi2Test>(new Chi2Test(options.options));
				break;
			}
			case wald_test: {
				models_map[id] = shared_ptr<WaldTest>(new WaldTest(options.options));
				break;
			}
			case lmm: {
				models_map[id] = shared_ptr<LinearMixedModel>(new LinearMixedModel(options.options));
				break;
			}
			case slmm: {
				models_map[id] = shared_ptr<SparseLMM>(new SparseLMM(options.options));
				break;
			}
			default:
				return 0;
		}
	}

	return id;
}

bool Scheduler::imputation(const job_id_t job_id) {
    if (JobIdUsed(job_id) && getJob(job_id)->model) {
        getJob(job_id)->model->imputation();
        return true;
    }
    return false;
}

bool Scheduler::setMetaData(const job_id_t job_id, const string& filename, vector<string>& marker_ids) {
    if (JobIdUsed(job_id)) {
        shared_ptr<Job_t> job = getJob(job_id);
        job->filename = filename;
        job->marker_ids = marker_ids;
    }
    return false;
}

bool Scheduler::setX(const job_id_t job_id, const Eigen::MatrixXf& X) {
	if (JobIdUsed(job_id) && getJob(job_id)->model) {
		getJob(job_id)->model->setX(X);
		return true;
	}
	return false;
}

bool Scheduler::setY(const job_id_t job_id, const Eigen::MatrixXf& Y) {
	if (JobIdUsed(job_id) && getJob(job_id)->model) {
		getJob(job_id)->model->setY(Y);
		return true;
	}
	return false;
}

// TODO: merge setX and setY into this function?
bool Scheduler::setModelAttributeMatrix(const job_id_t job_id, const string& str, Eigen::MatrixXf* Z) {
	try {
		if (JobIdUsed(job_id) && getJob(job_id)->model) {
			getJob(job_id)->model->setAttributeMatrix(str, Z);
			return true;
		}
		return false;
	} catch (const exception & e) {
		rethrow_exception(current_exception());
		return false;
	}
}

job_id_t Scheduler::newJob(const JobOptions_t& options) {
	Job_t* my_job = new Job_t();
	const job_id_t job_id = getNewJobId();
	if (ValidJobId(job_id)) {
		my_job->job_id = job_id;
		try {
			algorithm_id_t algorithm_id = newAlgorithm(options.alg_opts);
			my_job->algorithm = getAlgorithm(algorithm_id);
		} catch (const exception& e) {
			throw runtime_error("Error creating algorithm");
			return 0;
		}
		try {
			model_id_t model_id = newModel(options.model_opts);
			my_job->model = getModel(model_id);
			jobs_map[my_job->job_id] = shared_ptr<Job_t>(my_job);
			return job_id;
		} catch (const exception& e) {
			throw runtime_error("Error creating model");
			return 0;
		}
	} else {
		throw runtime_error("could not get a new job id (queue may be full)");
	}

	delete my_job;
	return 0;
}

bool Scheduler::startJob(const job_id_t job_id, void (*completion)(uv_work_t*, int)) {
	if (!ValidJobId(job_id)) {
		throw runtime_error("Job id must correspond to a job that has been created.");
		return false;
	}
	shared_ptr<Job_t> job = getJob(job_id);

	if (!job.get()) {
		throw runtime_error("Job must not be null.");
		return false;
	} else if (!job->algorithm || !job->model) {
		throw runtime_error("Job must have an algorithm and a model.");
		return false;
	} else if (job->algorithm->getIsRunning()) {
		throw runtime_error("Job is already running.");
		return false;
	}
	try {
		job->algorithm->assertReadyToRun();
		job->model->assertReadyToRun();
		job->request.data = job.get();
		uv_queue_work(uv_default_loop(), &(job->request), trainAlgorithmThread, completion);
		return true;
	} catch (const exception& ex) {
		rethrow_exception(current_exception());
		return false;
	}
}

// Runs in libuv thread spawned by startJob
void trainAlgorithmThread(uv_work_t* req) {
	Job_t* job = static_cast<Job_t*>(req->data);
	if (!job) {
		throw runtime_error("Job must not be null");
		return;
	}

	job->thread_id = std::this_thread::get_id();
	// TODO: as more algorithm/model types are created, add them here.
	try {
		if (!job->algorithm || !job->model) {
			throw runtime_error("Job must have an algorithm and a model");
		}
		job->algorithm->assertReadyToRun();
		job->model->assertReadyToRun();
		// Object slicing makes this annoying
		if (shared_ptr<BrentSearch> alg = dynamic_pointer_cast<BrentSearch>(job->algorithm)) {
			alg->setUpRun();
			if (shared_ptr<AdaMultiLasso> model = dynamic_pointer_cast<AdaMultiLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<Gflasso> model = dynamic_pointer_cast<Gflasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<MultiPopLasso> model = dynamic_pointer_cast<MultiPopLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<SparseLMM> model = dynamic_pointer_cast<SparseLMM>(job->model)) {
		        alg->sub_run(model);
		    } else if (shared_ptr<TreeLasso> model = dynamic_pointer_cast<TreeLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearMixedModel> model = dynamic_pointer_cast<LinearMixedModel>(job->model)) {
				alg->sub_run(model);
			} else {
		        throw runtime_error("Requested model type not implemented for the requested algorithm");
		    }
		    alg->finishRun();
		} else if (shared_ptr<GridSearch> alg = dynamic_pointer_cast<GridSearch>(job->algorithm)) {
			alg->setUpRun();
		    if (shared_ptr<AdaMultiLasso> model = dynamic_pointer_cast<AdaMultiLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<Gflasso> model = dynamic_pointer_cast<Gflasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<MultiPopLasso> model = dynamic_pointer_cast<MultiPopLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<SparseLMM> model = dynamic_pointer_cast<SparseLMM>(job->model)) {
		        alg->sub_run(model);
		    } else if (shared_ptr<TreeLasso> model = dynamic_pointer_cast<TreeLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearMixedModel> model = dynamic_pointer_cast<LinearMixedModel>(job->model)) {
				alg->run(model);
			} else {
		        throw runtime_error("Requested model type not implemented for the requested algorithm");
		    }
		    alg->finishRun();
		} else if (shared_ptr<IterativeUpdate> alg = dynamic_pointer_cast<IterativeUpdate>(job->algorithm)) {
			alg->setUpRun();
			if (shared_ptr<AdaMultiLasso> model = dynamic_pointer_cast<AdaMultiLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<Gflasso> model = dynamic_pointer_cast<Gflasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<MultiPopLasso> model = dynamic_pointer_cast<MultiPopLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<SparseLMM> model = dynamic_pointer_cast<SparseLMM>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<TreeLasso> model = dynamic_pointer_cast<TreeLasso>(job->model)) {
		        alg->run(model);
		    } else {
		        throw runtime_error("Requested model type not implemented for the requested algorithm");
		    }
		    alg->finishRun();
		} else if (shared_ptr<ProximalGradientDescent> alg = dynamic_pointer_cast<ProximalGradientDescent>(job->algorithm)) {
			alg->setUpRun();
			if (shared_ptr<AdaMultiLasso> model = dynamic_pointer_cast<AdaMultiLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<Gflasso> model = dynamic_pointer_cast<Gflasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<MultiPopLasso> model = dynamic_pointer_cast<MultiPopLasso>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<SparseLMM> model = dynamic_pointer_cast<SparseLMM>(job->model)) {
		        alg->run(model);
		    } else if (shared_ptr<TreeLasso> model = dynamic_pointer_cast<TreeLasso>(job->model)) {
		        alg->run(model);
		    } else {
		        throw runtime_error("Requested model type not implemented for the requested algorithm");
		    }
		    alg->finishRun();
		} else if (shared_ptr<HypoTestPlaceHolder> alg = dynamic_pointer_cast<HypoTestPlaceHolder>(job->algorithm)){
			alg->setUpRun();
			if (shared_ptr<FisherTest> model = dynamic_pointer_cast<FisherTest>(job->model)) {
				alg->run(model);
			} else if (shared_ptr<Chi2Test> model = dynamic_pointer_cast<Chi2Test>(job->model)) {
				alg->run(model);
			} else if (shared_ptr<WaldTest> model = dynamic_pointer_cast<WaldTest>(job->model)) {
				alg->run(model);
			} else {
				throw runtime_error("Requested model type not implemented for the requested algorithm");
			}
			alg->finishRun();
		} else if (shared_ptr<NeighborSelection> alg = dynamic_pointer_cast<NeighborSelection>(job->algorithm)) {
			alg->setUpRun();
			if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
				alg->run(model);
			} else {
				throw runtime_error("Requested model type not implemented for the requested algorithm");
			}
			alg->finishRun();
		} else if (shared_ptr<GraphicalLasso> alg = dynamic_pointer_cast<GraphicalLasso>(job->algorithm)) {
			alg->setUpRun();
			if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
				alg->run(model);
			} else {
				throw runtime_error("Requested model type not implemented for the requested algorithm");
			}
			alg->finishRun();
		} else {
			throw runtime_error("Requested algorithm type not implemented");
		}
	} catch (const exception& ex) {
		job->exception = current_exception();	// Must save the exception so that it can be passed between threads.
	}
}

float Scheduler::checkJobProgress(const job_id_t job_id) {
	if (JobIdUsed(job_id) && getJob(job_id)->algorithm) {
		return getJob(job_id)->algorithm->getProgress();
	}
	return -1;
}

bool Scheduler::cancelJob(const job_id_t job_id) {
	if (JobIdUsed(job_id) && getJob(job_id)->algorithm) {
		getJob(job_id)->algorithm->stop();
		return true;
	}
	return false;
}

bool Scheduler::deleteAlgorithm(const algorithm_id_t algorithm_id) {
	if (getAlgorithm(algorithm_id) && !getAlgorithm(algorithm_id)->getIsRunning()) {
		getAlgorithm(algorithm_id)->mtx.lock();
		algorithms_map[algorithm_id].reset();
		algorithms_map.erase(algorithm_id);
		return true;
	} else {
		return false;
	}
}

bool Scheduler::deleteModel(const model_id_t model_id) {
	if (getModel(model_id)) {
		/*getModel(model_id)->mtx.lock();*/
		models_map[model_id].reset();
		models_map.erase(model_id);
		return true;
	} else {
		return false;
	}
}

bool Scheduler::deleteJob(const job_id_t job_id) {
	if (JobIdUsed(job_id) && cancelJob(job_id)) {
		// Make sure the job is not currently running.
		getJob(job_id)->algorithm->mtx.lock();
		jobs_map[job_id].reset();
		jobs_map.erase(job_id);
		return true;
	}
	return false;
}

shared_ptr<Algorithm> Scheduler::getAlgorithm(const algorithm_id_t algorithm_id) {
	if (AlgorithmIdUsed(algorithm_id)) {
		return algorithms_map[algorithm_id];
	}
	throw runtime_error("Algorithm ID does not match any algorithms.");
	return shared_ptr<Algorithm>(nullptr);
}

shared_ptr<Model> Scheduler::getModel(const model_id_t model_id) {
	if (ModelIdUsed(model_id)) {
		return models_map[model_id];
	}
	throw runtime_error("Model ID does not match any models.");
	return shared_ptr<Model>(nullptr);
}

shared_ptr<Job_t> Scheduler::getJob(const job_id_t job_id) {
	if (JobIdUsed(job_id)) {
		return jobs_map[job_id];
	}
	throw runtime_error("Job ID does not match any jobs.");
	return shared_ptr<Job_t>(nullptr);
}

MatrixXf Scheduler::getJobResult(const job_id_t job_id) {
	if (JobIdUsed(job_id)) {
		shared_ptr<Job_t> job = getJob(job_id);
		if (shared_ptr<AdaMultiLasso> model = dynamic_pointer_cast<AdaMultiLasso>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<Gflasso> model = dynamic_pointer_cast<Gflasso>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<LinearRegression> model = dynamic_pointer_cast<LinearRegression>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<MultiPopLasso> model = dynamic_pointer_cast<MultiPopLasso>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<SparseLMM> model = dynamic_pointer_cast<SparseLMM>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<TreeLasso> model = dynamic_pointer_cast<TreeLasso>(job->model)) {
	        return model->getBeta();
	    } else if (shared_ptr<LinearMixedModel> model = dynamic_pointer_cast<LinearMixedModel>(job->model)) {
			return model->getBeta();
		} else if (shared_ptr<FisherTest> model = dynamic_pointer_cast<FisherTest>(job->model)) {
			return model->getBeta();
		} else if (shared_ptr<Chi2Test> model = dynamic_pointer_cast<Chi2Test>(job->model)) {
			return model->getBeta();
		} else if (shared_ptr<WaldTest> model = dynamic_pointer_cast<WaldTest>(job->model)) {
			return model->getBeta();
		} else {
	    	return model->getBeta();
	    }
	} else {
		return MatrixXf();
	}
}

////////////////////////////////////////////////////////
// Private Functions
////////////////////////////////////////////////////////

model_id_t Scheduler::getNewModelId() {
	model_id_t candidate_model_id = next_model_id;
	for (unsigned int i = 1; i < kMaxModelId; i++) {
		candidate_model_id = (candidate_model_id + 1) % kMaxModelId + 1;
		if (!ModelIdUsed(candidate_model_id)) {
			model_id_t retval = next_model_id;
			next_model_id = candidate_model_id;
			return retval;
		}
	}
	return 0;
}

algorithm_id_t Scheduler::getNewAlgorithmId() {
	algorithm_id_t candidate_algorithm_id = next_algorithm_id;
	for (unsigned int i = 1; i < kMaxAlgorithmId; i++) {
		candidate_algorithm_id = (candidate_algorithm_id + 1) % kMaxAlgorithmId + 1;
		if (!AlgorithmIdUsed(candidate_algorithm_id)) {
			algorithm_id_t retval = next_algorithm_id;
			next_algorithm_id = candidate_algorithm_id;
			return retval;
		}
	}
	return 0;
}

job_id_t Scheduler::getNewJobId() {
	job_id_t candidate_job_id = next_job_id;
	for (unsigned int i = 1; i < kMaxJobId; i++) {
		candidate_job_id = (candidate_job_id + i) % kMaxJobId + 1;
		if (!JobIdUsed(candidate_job_id)) {
			job_id_t retval = next_job_id;
			next_job_id = candidate_job_id;
			return retval;
		}
	}
	return 0;
}

bool Scheduler::ValidAlgorithmId(const algorithm_id_t id) {
	return (id > 0 && id <= kMaxAlgorithmId);
}

bool Scheduler::ValidModelId(const model_id_t id) {
	return (id > 0 && id <= kMaxModelId);
}

bool Scheduler::ValidJobId(const job_id_t id) {
	return (id > 0 && id <= kMaxJobId);
}

bool Scheduler::AlgorithmIdUsed(const algorithm_id_t id) {
	return (ValidAlgorithmId(id) && algorithms_map[id] && algorithms_map[id].get());
}

bool Scheduler::ModelIdUsed(const model_id_t id) {
	return (ValidModelId(id) && models_map[id] && models_map[id].get());
}

bool Scheduler::JobIdUsed(const job_id_t id) {
	return (ValidJobId(id) && jobs_map[id] && jobs_map[id].get());
}