//  Copyright John Maddock 2012 - 2021.
//  Copyright Christopher Kormanyos 2016 - 2021.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

// Wrapper that works with e_float-2021, see also
// https://github.com/ckormanyos/e_float-2021

#ifndef E_FLOAT_2017_08_18_HPP_
  #define E_FLOAT_2017_08_18_HPP_

  #include <cstdint>
  #include <sstream>
  #include <tuple>
  #include <type_traits>

  #include <e_float/e_float.h>
  #include <e_float/e_float_functions.h>

  #include <boost/config.hpp>
  #include <boost/multiprecision/number.hpp>

  namespace boost { namespace math { namespace ef {

  // Forward declaration of the e_float multiple precision class.
  // This class binds native ::e_float to boost::multiprecsion::e_float.
  class e_float;

  } } }

  // Define the number category as a floating-point kind
  // for the e_float. This is needed for properly
  // interacting as a backend with boost::muliprecision.
  #if (BOOST_VERSION <= 107200)
  template<>
  struct boost::multiprecision::number_category<boost::math::ef::e_float>
    : public boost::integral_constant<int, boost::multiprecision::number_kind_floating_point> { };
  #elif (BOOST_VERSION <= 107500)
  template<>
  struct boost::multiprecision::number_category<boost::math::ef::e_float>
    : public boost::integral_constant<int, boost::multiprecision::number_kind_floating_point> { };
  #else
  template<>
  struct boost::multiprecision::number_category<boost::math::ef::e_float>
    : public std::integral_constant<int, boost::multiprecision::number_kind_floating_point> { };
  #endif

  namespace boost { namespace math { namespace ef {

  // This is the e_float multiple precision class.
  class e_float
  {
  public:
    #if (BOOST_VERSION <= 107500)
    using signed_types   = mpl::list<std::int64_t>;
    using unsigned_types = mpl::list<std::uint64_t>;
    using float_types    = mpl::list<long double>;
    #else
    using   signed_types = std::tuple<  signed char,   signed short,   signed int,   signed long,   signed long long, std::intmax_t>;
    using unsigned_types = std::tuple<unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, std::uintmax_t>;
    using float_types    = std::tuple<float, double, long double>;
    #endif
    using exponent_type  = std::int64_t;

    e_float() : m_value() { }

    explicit e_float(const ::e_float& rep) : m_value(rep) { }

    e_float(const e_float& other) : m_value(other.m_value) { }
    e_float(e_float&& other) : m_value(static_cast<::e_float&&>(other.m_value)) { }

    template<typename UnsignedIntegralType,
             typename std::enable_if<(   (std::is_integral<UnsignedIntegralType>::value == true)
                                      && (std::is_unsigned<UnsignedIntegralType>::value == true))>::type const* = nullptr>
    e_float(UnsignedIntegralType u) : m_value(::e_float(std::uint64_t(u))) { }

    template<typename SignedIntegralType,
             typename std::enable_if<(   (std::is_integral<SignedIntegralType>::value == true)
                                      && (std::is_signed  <SignedIntegralType>::value == true))>::type const* = nullptr>
    e_float(SignedIntegralType n) : m_value(::e_float(std::int64_t(n))) { }

    template<typename FloatingPointType,
             typename std::enable_if<std::is_floating_point<FloatingPointType>::value == true>::type const* = nullptr>
    e_float(FloatingPointType f) : m_value(::e_float(static_cast<long double>(f))) { }

    e_float(const char* c) : m_value(c) { }

    e_float(const std::string& str) : m_value(str) { }

    ~e_float() { }

    e_float& operator=(const e_float& other)
    {
      if(this != &other)
      {
        m_value = other.m_value;
      }

      return *this;
    }

    e_float& operator=(e_float&& other)
    {
      m_value.operator=(static_cast<::e_float&&>(other.m_value));

      return *this;
    }

    template<typename ArithmeticType,
             typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
    e_float& operator=(const ArithmeticType& x)
    {
      m_value = ::e_float(x);

      return *this;
    }

    e_float& operator=(const std::string& str_rep)  { m_value = ::e_float(str_rep);  return *this; }
    e_float& operator=(const char*        char_ptr) { m_value = ::e_float(char_ptr); return *this; }

    void swap(e_float& other_mp_cpp_backend)
    {
      m_value.swap(other_mp_cpp_backend.m_value);
    }

          ::e_float&  representation()       { return m_value; }
    const ::e_float&  representation() const { return m_value; }
    const ::e_float& crepresentation() const { return m_value; }

    std::string str(std::streamsize number_of_digits, const std::ios::fmtflags format_flags) const
    {
      std::string        my_result_str;
      std::stringstream  my_stream_str;

      my_stream_str.flags(format_flags);

      static_cast<void>(my_stream_str.precision(number_of_digits));

      m_value.wr_string(my_result_str, my_stream_str);

      return my_result_str;
    }

    void negate()
    {
      m_value.negate();
    }

    int compare(const e_float& other_mp_cpp_backend) const
    {
      return static_cast<int>(m_value.compare(other_mp_cpp_backend.m_value));
    }

    template<typename ArithmeticType,
             typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
    int compare(ArithmeticType x)
    {
      return static_cast<int>(m_value.compare(::e_float(x)));
    }

  private:
    ::e_float m_value;

    e_float& operator=(const ::e_float&) = delete;
  };

  inline void eval_add(e_float& result, const e_float& x)
  {
    result.representation() += x.crepresentation();
  }

  inline void eval_subtract(e_float& result, const e_float& x)
  {
    result.representation() -= x.crepresentation();
  }

  inline void eval_multiply(e_float& result, const e_float& x)
  {
    result.representation() *= x.crepresentation();
  }

  inline void eval_multiply(e_float& result, const e_float& x, const e_float& y)
  {
    result.representation() = x.crepresentation() * y.crepresentation();
  }

  template<typename SignedIntegralType,
           typename std::enable_if<(   (std::is_integral<SignedIntegralType>::value == true)
                                    && (std::is_signed  <SignedIntegralType>::value == true))>::type const* = nullptr>
  void eval_multiply(e_float& result, const SignedIntegralType& n)
  {
    result.representation().mul_signed_long_long(static_cast<std::int64_t>(n));
  }

  inline void eval_divide(e_float& result, const e_float& x)
  {
    result.representation() /= x.crepresentation();
  }

  template<typename SignedIntegralType,
           typename std::enable_if<(   (std::is_integral<SignedIntegralType>::value == true)
                                    && (std::is_signed  <SignedIntegralType>::value == true))>::type const* = nullptr>
  void eval_divide(e_float& result, const SignedIntegralType& n)
  {
    result.representation().div_signed_long_long(static_cast<std::int64_t>(n));
  }

  inline bool eval_eq(const e_float& a, const e_float& b)
  {
    return (a.compare(b) == 0);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
  bool eval_eq(const e_float& a, const ArithmeticType& b)
  {
    return (a.compare(b) == 0);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
  bool eval_eq(const ArithmeticType& a, const e_float& b)
  {
    return (e_float(a).compare(b) == 0);
  }

  inline bool eval_gt(const e_float& a, const e_float& b)
  {
    return (a.compare(b) == 1);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
  bool eval_gt(const e_float& a, const ArithmeticType& b)
  {
    return (a.compare(b) == 1);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value>::type const* = nullptr>
  bool eval_gt(const ArithmeticType& a, const e_float& b)
  {
    return (e_float(a).compare(b) == 1);
  }

  inline bool eval_lt(const e_float& a, const e_float& b)
  {
    return (a.compare(b) == -1);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
  bool eval_lt(const e_float& a, const ArithmeticType& b)
  {
    return (a.compare(b) == -1);
  }

  template<typename ArithmeticType,
           typename std::enable_if<std::is_arithmetic<ArithmeticType>::value == true>::type const* = nullptr>
  bool eval_lt(const ArithmeticType& a, const e_float& b)
  {
    return (e_float(a).compare(b) == -1);
  }

  inline bool eval_is_zero(const e_float& x)
  {
    return x.crepresentation().iszero();
  }

  inline int eval_get_sign(const e_float& x)
  {
    if     (x.crepresentation().iszero()) { return  0; }
    else if(x.crepresentation().isneg ()) { return -1; }
    else                                  { return  1; }
  }

  inline void eval_convert_to(unsigned long long* result,
                              const e_float& val)
  {
    *result = (val.crepresentation()).extract_unsigned_long_long();
  }

  inline void eval_convert_to(signed long long* result,
                              const e_float& val)
  {
    *result = (val.crepresentation()).extract_signed_long_long();
  }

  inline void eval_convert_to(long double* result,
                              const e_float& val)
  {
    *result = (val.crepresentation()).extract_long_double();
  }

  inline void eval_frexp(      e_float&                         result,
                         const e_float&                         x,
                               typename e_float::exponent_type* expptr)
  {
    using local_exponent_type = int;

    local_exponent_type exp2;

    result.representation() = ::ef::frexp(x.crepresentation(), &exp2);

    *expptr = static_cast<typename e_float::exponent_type>(exp2);
  }

  inline void eval_frexp(      e_float& result,
                         const e_float& x,
                         int*           expptr,
                         typename std::enable_if<std::is_same<typename e_float::exponent_type, int>::value == false>::type const* = nullptr)
  {
    result.representation() = ::ef::frexp(x.crepresentation(), expptr);
  }

  inline void eval_ldexp(      e_float& result,
                         const e_float& x,
                         const typename e_float::exponent_type exp_value)
  {
    using local_exponent_type = int;

    result.representation() = ::ef::ldexp(x.crepresentation(), local_exponent_type(exp_value));
  }

  inline void eval_ldexp(      e_float& result,
                         const e_float& x,
                         int            exp_value,
                         typename std::enable_if<std::is_same<typename e_float::exponent_type, int>::value == false>::type const* = nullptr)
  {
    using local_exponent_type = int;

    result.representation() = ::ef::ldexp(x.crepresentation(), local_exponent_type(exp_value));
  }

  inline e_float::exponent_type eval_ilogb(const e_float& val)
  {
    if(val.crepresentation().iszero())
    {
      return (std::numeric_limits<e_float::exponent_type>::min)();
    }

    if ((val.crepresentation().isinf)())
    {
      return (std::numeric_limits<e_float::exponent_type>::max)();
    }

    if ((val.crepresentation().isnan)())
    {
      #if defined(FP_ILOGBNAN)
      return FP_ILOGBNAN;
      #else
      return (std::numeric_limits<e_float::exponent_type>::max)();
      #endif
    }

   // Set the result to base-10 exponent (order) of the value.
   return val.crepresentation().order();
  }

  template<typename IntegralType,
           typename std::enable_if<(   (std::is_fundamental<IntegralType>::value == true)
                                    && (std::is_integral   <IntegralType>::value == true))>::type const* = nullptr>
  void eval_scalbn(e_float& result, const e_float& val, IntegralType e)
  {
    const std::int64_t my_e = static_cast<std::int64_t>(e);

    const e_float t(::e_float(1.0, my_e));

    eval_multiply(result, val, t);
  }

  inline void eval_floor(      e_float& result,
                         const e_float& x)
  {
    result.representation() = ::ef::floor(x.crepresentation());
  }

  inline void eval_ceil(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::ceil(x.crepresentation());
  }

  inline int eval_fpclassify(const e_float& x)
  {
    if     ((x.crepresentation().isinf)()) { return FP_INFINITE; }
    else if((x.crepresentation().isnan)()) { return FP_NAN; }
    else if( x.crepresentation().iszero()) { return FP_ZERO; }
    else                                   { return FP_NORMAL; }
  }

  inline void eval_trunc(      e_float& result,
                         const e_float& x)
  {
    result.representation() = ::ef::integer_part(x.crepresentation());
  }

  inline void eval_abs(      e_float& result,
                       const e_float& x)
  {
    result.representation() = x.crepresentation();

    if(result.crepresentation().isneg())
    {
      result.representation().negate();
    }
  }

  inline void eval_fabs(      e_float& result,
                        const e_float& x)
  {
    result.representation() = x.crepresentation();

    if(result.crepresentation().isneg())
    {
      result.representation().negate();
    }
  }

  inline void eval_sqrt(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::sqrt(x.crepresentation());
  }

  inline void eval_sin(      e_float& result,
                       const e_float& x)
  {
    result.representation() = ::ef::sin(x.crepresentation());
  }

  inline void eval_cos(      e_float& result,
                       const e_float& x)
  {
    result.representation() = ::ef::cos(x.crepresentation());
  }

  inline void eval_tan(      e_float& result,
                       const e_float& x)
  {
    result.representation() = ::ef::tan(x.crepresentation());
  }

  inline void eval_asin(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::asin(x.crepresentation());
  }

  inline void eval_acos(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::acos(x.crepresentation());
  }

  inline void eval_atan(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::atan(x.crepresentation());
  }

  inline void eval_atan2(      e_float& result,
                         const e_float& y,
                         const e_float& x)
  {
    result.representation() = ::ef::atan2(y.crepresentation(), x.crepresentation());
  }

  inline void eval_log(      e_float& result,
                       const e_float& x)
  {
    result.representation() = ::ef::log(x.crepresentation());
  }

  inline void eval_log10(      e_float& result,
                         const e_float& x)
  {
    result.representation() = ::ef::log10(x.crepresentation());
  }

  inline void eval_exp(      e_float& result,
                       const e_float& x)
  {
    result.representation() = ::ef::exp(x.crepresentation());
  }

  inline void eval_sinh(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::sinh(x.crepresentation());
  }

  inline void eval_cosh(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::cosh(x.crepresentation());
  }

  inline void eval_tanh(      e_float& result,
                        const e_float& x)
  {
    result.representation() = ::ef::tanh(x.crepresentation());
  }

  inline void eval_fmod(      e_float& result,
                        const e_float& x,
                        const e_float& y)
  {
    if(y.crepresentation().iszero())
    {
      result.representation() = ::ef::zero();
    }
    else
    {
      // Calculate the fractional part of x such that:
      //   x = (integer_part * y) + fractional_part,
      // where
      //   |fractional_part| < |y|,
      // and fractional_part has the same sign as x.

      const ::e_float integer_part = ::ef::floor(x.crepresentation() / y.crepresentation());

      result.representation() =
        x.crepresentation() - (integer_part * y.crepresentation());

      if(x.crepresentation().isneg() != y.crepresentation().isneg())
      {
        result.representation() -= y.crepresentation();
      }
    }
  }

  inline void eval_pow(      e_float& result,
                       const e_float& x,
                       const e_float& a)
  {
    result.representation() = ::ef::pow(x.crepresentation(), a.crepresentation());
  }

  } } } // namespace boost::math::ef

  namespace boost { namespace math { namespace policies {

  // Specialization of the precision structure.
  template<typename ThisPolicy,
           const boost::multiprecision::expression_template_option ExpressionTemplates>
  struct precision<boost::multiprecision::number<boost::math::ef::e_float,
                                                 ExpressionTemplates>,
                   ThisPolicy>
  {
    using precision_type = typename ThisPolicy::precision_type;

    using local_digits_2 = digits2<((::e_float::ef_digits10 + 1LL) * 1000LL) / 301LL>;

    #if (BOOST_VERSION <= 107500)
    using type = typename mpl::if_c       <((local_digits_2::value <= precision_type::value) || (precision_type::value <= 0)),
                                           local_digits_2,
                                           precision_type>::type;
    #else
    using type = typename std::conditional<((local_digits_2::value <= precision_type::value) || (precision_type::value <= 0)),
                                           local_digits_2,
                                           precision_type>::type;
    #endif
  };

  } } } // namespaces boost::math::policies

  namespace std
  {
    template<const boost::multiprecision::expression_template_option ExpressionTemplates>
    class numeric_limits<boost::multiprecision::number<boost::math::ef::e_float,
                                                       ExpressionTemplates>>
    {
    public:
      static constexpr bool is_specialized = true;
      static constexpr bool is_signed      = true;
      static constexpr bool is_integer     = false;
      static constexpr bool is_exact       = false;
      static constexpr bool is_bounded     = true;
      static constexpr bool is_modulo      = false;
      static constexpr bool is_iec559      = false;
      static constexpr int  digits         = ::e_float::ef_digits10;
      static constexpr int  digits10       = ::e_float::ef_digits10;
      static constexpr int  max_digits10   = ::e_float::ef_digits10 + 1;

      static constexpr std::int64_t max_exponent   = ::e_float::ef_max_exp;
      static constexpr std::int64_t max_exponent10 = ::e_float::ef_max_exp10;
      static constexpr std::int64_t min_exponent   = ::e_float::ef_min_exp;
      static constexpr std::int64_t min_exponent10 = ::e_float::ef_min_exp10;

      static constexpr int                     radix             = 10;
      static constexpr std::float_round_style  round_style       = std::round_indeterminate;
      static constexpr bool                    has_infinity      = true;
      static constexpr bool                    has_quiet_NaN     = true;
      static constexpr bool                    has_signaling_NaN = false;
      static constexpr std::float_denorm_style has_denorm        = std::denorm_absent;
      static constexpr bool                    has_denorm_loss   = false;
      static constexpr bool                    traps             = false;
      static constexpr bool                    tinyness_before   = false;

      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> (min)        () { return boost::math::ef::e_float(ef::value_min()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> (max)        () { return boost::math::ef::e_float(ef::value_max()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> lowest       () { return boost::math::ef::e_float(ef::zero()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> epsilon      () { return boost::math::ef::e_float(ef::value_eps()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> round_error  () { return boost::math::ef::e_float(ef::half()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> infinity     () { return boost::math::ef::e_float(ef::value_inf()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> quiet_NaN    () { return boost::math::ef::e_float(ef::value_nan()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> signaling_NaN() { return boost::math::ef::e_float(ef::zero()); }
      static constexpr boost::multiprecision::number<boost::math::ef::e_float, ExpressionTemplates> denorm_min   () { return boost::math::ef::e_float(ef::zero()); }
    };
  } // namespace std

#endif // E_FLOAT_2017_08_18_HPP_


// Boost.Geometry

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

// This file was modified by Oracle on 2014-2021.
// Modifications copyright (c) 2014-2021, Oracle and/or its affiliates.
// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle
// 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)

#ifndef BOOST_GEOMETRY_ALGORITHMS_AZIMUTH_HPP
#define BOOST_GEOMETRY_ALGORITHMS_AZIMUTH_HPP

#include <boost/geometry/algorithms/not_implemented.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/radian_access.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/strategies/default_strategy.hpp>
#include <boost/geometry/strategies/azimuth/cartesian.hpp>
#include <boost/geometry/strategies/azimuth/geographic.hpp>
#include <boost/geometry/strategies/azimuth/spherical.hpp>

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

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename Geometry1, typename Geometry2,
    typename Tag1 = typename tag<Geometry1>::type,
    typename Tag2 = typename tag<Geometry2>::type
>
struct azimuth : not_implemented<Tag1, Tag2>
{};

template <typename Point1, typename Point2>
struct azimuth<Point1, Point2, point_tag, point_tag>
{
    template <typename Strategy>
    static auto apply(Point1 const& p1, Point2 const& p2, Strategy const& strategy)
    {
        typedef typename decltype(strategy.azimuth())::template result_type
            <
                typename coordinate_type<Point1>::type,
                typename coordinate_type<Point2>::type
            >::type calc_t;

        calc_t result = 0;
        calc_t const x1 = geometry::get_as_radian<0>(p1);
        calc_t const y1 = geometry::get_as_radian<1>(p1);
        calc_t const x2 = geometry::get_as_radian<0>(p2);
        calc_t const y2 = geometry::get_as_radian<1>(p2);

        strategy.azimuth().apply(x1, y1, x2, y2, result);

        // NOTE: It is not clear which units we should use for the result.
        //   For now radians are always returned but a user could expect
        //   e.g. something like this:
        /*
        bool const both_degree = std::is_same
                <
                    typename detail::cs_angular_units<Point1>::type,
                    geometry::degree
                >::value
            && std::is_same
                <
                    typename detail::cs_angular_units<Point2>::type,
                    geometry::degree
                >::value;
        if (both_degree)
        {
            result *= math::r2d<calc_t>();
        }
        */

        return result;
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

namespace resolve_strategy
{

template
<
    typename Strategy,
    bool IsUmbrella = strategies::detail::is_umbrella_strategy<Strategy>::value
>
struct azimuth
{
    template <typename P1, typename P2>
    static auto apply(P1 const& p1, P2 const& p2, Strategy const& strategy)
    {
        return dispatch::azimuth<P1, P2>::apply(p1, p2, strategy);
    }
};

template <typename Strategy>
struct azimuth<Strategy, false>
{
    template <typename P1, typename P2>
    static auto apply(P1 const& p1, P2 const& p2, Strategy const& strategy)
    {
        using strategies::azimuth::services::strategy_converter;
        return dispatch::azimuth
            <
                P1, P2
            >::apply(p1, p2, strategy_converter<Strategy>::get(strategy));
    }
};

template <>
struct azimuth<default_strategy, false>
{
    template <typename P1, typename P2>
    static auto apply(P1 const& p1, P2 const& p2, default_strategy)
    {
        typedef typename strategies::azimuth::services::default_strategy
            <
                P1, P2
            >::type strategy_type;

        return dispatch::azimuth<P1, P2>::apply(p1, p2, strategy_type());
    }
};

} // namespace resolve_strategy

namespace resolve_variant
{
} // namespace resolve_variant

/*!
\brief Calculate azimuth of a segment defined by a pair of points.
\ingroup azimuth
\tparam Point1 Type of the first point of a segment.
\tparam Point2 Type of the second point of a segment.
\param point1 First point of a segment.
\param point2 Second point of a segment.
\return Azimuth in radians.

\qbk{[include reference/algorithms/azimuth.qbk]}

\qbk{
[heading Example]
[azimuth]
[azimuth_output]
}
*/
template <typename Point1, typename Point2>
inline auto azimuth(Point1 const& point1, Point2 const& point2)
{
    concepts::check<Point1 const>();
    concepts::check<Point2 const>();

    return resolve_strategy::azimuth
            <
                default_strategy
            >::apply(point1, point2, default_strategy());
}

/*!
\brief Calculate azimuth of a segment defined by a pair of points.
\ingroup azimuth
\tparam Point1 Type of the first point of a segment.
\tparam Point2 Type of the second point of a segment.
\tparam Strategy Type of an umbrella strategy defining azimuth strategy.
\param point1 First point of a segment.
\param point2 Second point of a segment.
\param strategy Umbrella strategy defining azimuth strategy.
\return Azimuth in radians.

\qbk{distinguish,with strategy}
\qbk{[include reference/algorithms/azimuth.qbk]}

\qbk{
[heading Example]
[azimuth_strategy]
[azimuth_strategy_output]
}
*/
template <typename Point1, typename Point2, typename Strategy>
inline auto azimuth(Point1 const& point1, Point2 const& point2, Strategy const& strategy)
{
    concepts::check<Point1 const>();
    concepts::check<Point2 const>();

    return resolve_strategy::azimuth<Strategy>::apply(point1, point2, strategy);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_AZIMUTH_HPP


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#include "poplibs_support/logging.hpp"
#include "poplin/ConvUtil.hpp"
#include "poplin/Convolution.hpp"
#include "popops/ElementWise.hpp"
#include "popops/Rearrange.hpp"
#include "popops/Reduce.hpp"
#include "popops/ScaledAdd.hpp"
#include "poputil/DebugInfo.hpp"
#include "poputil/TileMapping.hpp"
#include "poputil/Util.hpp"
#include "poputil/VertexTemplates.hpp"
#include "poputil/exceptions.hpp"
#include <boost/icl/interval_map.hpp>
#include <cassert>
#include <cmath>
#include <set>

using namespace poplar;
using namespace poplar::program;
using namespace poputil;
using namespace popops;
namespace logging = poplibs_support::logging;

namespace poplin {

static Tensor normReduce(Graph &graph, const Tensor &actsUngrouped,
                         const Tensor &scale, bool doSquare,
                         std::vector<ComputeSet> &css,
                         const Type &, // partialsType,
                         const Type &outputType,
                         const Tensor *outputToCloneFrom,
                         const DebugNameAndId &dnai) {
  std::string layer = "ReduceResult";
  Tensor t;

  // The output tensor mapping may be specified or created
  if (outputToCloneFrom) {
    t = graph.clone(outputType, *outputToCloneFrom, {dnai, layer});
  } else {
    t = createBroadcastOperand(graph, actsUngrouped, outputType, 1, true,
                               {dnai, layer});
  }

  if (actsUngrouped.rank() < 2)
    throw poplibs_error("NormReduce with rank " +
                        std::to_string(actsUngrouped.rank()) + " expected >=2");

  std::vector<std::size_t> reduceDims(actsUngrouped.rank() - 1);
  std::iota(reduceDims.begin() + 1, reduceDims.end(), 2);

  popops::reduceWithOutput(
      graph, actsUngrouped, t, reduceDims,
      {doSquare ? popops::Operation::SQUARE_ADD : popops::Operation::ADD, false,
       scale},
      css, {dnai});
  return t;
}

static Tensor normReduce(Graph &graph, const Tensor &actsUngrouped, float scale,
                         bool doSquare, std::vector<ComputeSet> &css,
                         const Type &partialsType, const Type &outputType,
                         const Tensor *outputToCloneFrom,
                         const DebugNameAndId &dnai) {
  auto constantScale =
      graph.addConstant(FLOAT, {}, scale, {dnai, "constantScale"});
  graph.setTileMapping(constantScale, 0);

  return normReduce(graph, actsUngrouped, constantScale, doSquare, css,
                    partialsType, outputType, outputToCloneFrom,
                    {dnai, "ConstScale"});
}

static Tensor computeInvStdDev(Graph &graph, const Tensor &mean,
                               const Tensor &power, float eps, float scaleVar,
                               Sequence &prog, const Type &invStdDevType,
                               bool stableAlgo, const DebugNameAndId &dnai) {
  const auto meanType = mean.elementType();
  const auto powerType = power.elementType();
  auto iStdDev = graph.clone(invStdDevType, mean, {dnai, "iStdDev"});

  const auto meanFlat = mean.flatten();
  const auto powerFlat = power.flatten();
  const auto iStdDevFlat = iStdDev.flatten();

  const auto &target = graph.getTarget();
  const auto numTiles = target.getNumTiles();
  const auto cs = graph.addComputeSet({dnai, "iStdDev"});

  const auto mapping = graph.getTileMapping(iStdDev);
  const auto grainSize = target.getVectorWidth(invStdDevType);

  for (auto tile = 0U; tile != numTiles; ++tile) {
    const auto tileContiguousRegions =
        graph.getSortedContiguousRegions(iStdDevFlat, mapping[tile]);
    auto vertexRegions = splitRegionsBetweenWorkers(
        target, tileContiguousRegions, grainSize, 2 * grainSize);

    for (const auto &regions : vertexRegions) {
      auto v = graph.addVertex(cs,
                               templateVertex("poplin::InverseStdDeviation",
                                              meanType, powerType,
                                              invStdDevType, stableAlgo),
                               {{"mean", meanFlat.slices(regions)},
                                {"power", powerFlat.slices(regions)},
                                {"iStdDev", iStdDevFlat.slices(regions)}});
      graph.setInitialValue(v["eps"], eps);
      graph.setInitialValue(v["scaleVar"], scaleVar);
      graph.setTileMapping(v, tile);
    }
  }
  prog.add(Execute(cs, {dnai}));
  return iStdDev;
}

static Tensor broadcastChannelToMatch(const Tensor &ref, const Tensor &t) {
  return t.flatten().expand(std::vector<std::size_t>(ref.rank() - 2, 1));
}

std::pair<Tensor, Tensor>
normStatistics(Graph &graph, const Tensor &acts, float eps, Sequence &prog,
               bool unbiasedVarEstimate, bool stableAlgo,
               const Type &partialsType,
               const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(
      debugContext,
      DI_ARGS(acts, eps, unbiasedVarEstimate, stableAlgo, partialsType));

  const std::string layer = "Norm/statistics";
  logging::poplin::info(
      "normStatistics acts={}, eps={}, unbiasedVarEstimate={}, "
      "type={}, name={}",
      acts.shape(), eps, unbiasedVarEstimate, partialsType,
      debugContext.getPathName() + "/" + layer);

  size_t numElements = acts.numElements();

  // Avoid a possible divide by zero FP exception.
  // Note that numElements will be 0 if acts.dim(1) is 0.
  if (acts.dim(1) > 0)
    numElements /= acts.dim(1);

  float scale = 1.0f;
  float scaleVar = 1.0f;
  if (numElements > 1) {
    scale /= numElements;
    if (unbiasedVarEstimate)
      scaleVar = static_cast<float>(numElements) / (numElements - 1);
  }

  const auto powerOutputType = partialsType;
  const auto meanOutputType = acts.elementType();

  std::vector<ComputeSet> css;
  auto mean = normReduce(graph, acts, scale, false, css, partialsType,
                         meanOutputType, nullptr, {di, layer + "/mean"});

  auto maybeZeroMeanActs = acts;
  if (stableAlgo) {
    for (const auto &cs : css) {
      prog.add(Execute(cs, {di}));
    }
    css.clear();
    logging::poplin::info("Stable statistics estimator used");
    using namespace popops::expr;
    maybeZeroMeanActs = popops::map(graph, _1 - Cast(_2, acts.elementType()),
                                    {acts, broadcastChannelToMatch(acts, mean)},
                                    prog, {di, layer + "/removeMean"});
  }
  // The actual output type for squared sum may be different as the dynamic
  // range is higher. The selection should be based on actual statistics
  // gathered from training experiments. For now keep it at reduced precision
  // to save memory
  auto power =
      normReduce(graph, maybeZeroMeanActs, scale, true, css, partialsType,
                 powerOutputType, &mean, {di, layer + "/power"});

  for (const auto &cs : css) {
    prog.add(Execute(cs, {di}));
  }

  auto iStdDev = computeInvStdDev(graph, mean, power, eps, scaleVar, prog,
                                  acts.elementType(), stableAlgo, {di});
  di.addOutputs(DI_ARGS(mean, iStdDev));
  return std::make_pair(mean, iStdDev);
}

Tensor createNormGamma(Graph &graph, const Tensor &acts, const Type &type,
                       const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts, type));
  auto output =
      createBroadcastOperand(graph, acts, type, 1, true, {di, "gamma"});
  di.addOutput(output);
  return output;
}

Tensor createNormGamma(Graph &graph, const Tensor &acts,
                       const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts));
  auto output = createNormGamma(graph, acts, acts.elementType(), {di});
  di.addOutput(output);
  return output;
}

Tensor createNormBeta(Graph &graph, const Tensor &acts, const Type &type,
                      const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts, type));
  auto output =
      createBroadcastOperand(graph, acts, type, 1, true, {di, "beta"});
  di.addOutput(output);
  return output;
}

Tensor createNormBeta(Graph &graph, const Tensor &acts,
                      const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts));
  auto output = createNormBeta(graph, acts, acts.elementType(), {di});
  di.addOutput(output);
  return output;
}

std::pair<Tensor, Tensor>
createNormParams(Graph &graph, const Tensor &acts,
                 const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts));
  auto gamma = createNormGamma(graph, acts, {di});
  auto beta = createNormBeta(graph, acts, {di});
  di.addOutputs(DI_ARGS(gamma, beta));
  return std::make_pair(gamma, beta);
}

Tensor normWhiten(Graph &graph, const Tensor &acts, const Tensor &mean,
                  const Tensor &iStdDev, Sequence &prog,
                  const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(acts, mean, iStdDev));

  const std::string layer = "Whiten";
  logging::poplin::info("normWhiten acts={}, mean={}, iStdDev={}, name={}",
                        acts.shape(), mean.shape(), iStdDev.shape(),
                        debugContext.getPathName() + "/" + layer);

  auto meanBroadcast = broadcastChannelToMatch(acts, mean);
  auto actsWhitened =
      sub(graph, acts, meanBroadcast, prog, {di, layer + "/mean"});
  auto iStdDevBroadcast = broadcastChannelToMatch(actsWhitened, iStdDev);
  mulInPlace(graph, actsWhitened, iStdDevBroadcast, prog,
             {di, layer + "/istdDev"});
  di.addOutput(actsWhitened);
  return actsWhitened;
}

Tensor normalise(Graph &graph, const Tensor &actsWhitened, const Tensor &gamma,
                 const Tensor &beta, Sequence &prog,
                 const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext,
                                 DI_ARGS(actsWhitened, gamma, beta));

  const std::string layer = "Norm/normalise";
  logging::poplin::info("normalise actsWhitened={}, gamma={}, beta={}, name={}",
                        actsWhitened.shape(), gamma.shape(), beta.shape(),
                        debugContext.getPathName() + "/" + layer);

  auto gammaBroadcast = broadcastChannelToMatch(actsWhitened, gamma);
  auto actsNormalised =
      mul(graph, actsWhitened, gammaBroadcast, prog, {di, layer + "/gamma"});
  auto betaBroadcast = broadcastChannelToMatch(actsNormalised, beta);
  addInPlace(graph, actsNormalised, betaBroadcast, prog, {di, layer + "/beta"});
  di.addOutput(actsNormalised);
  return actsNormalised;
}

static std::pair<Tensor, Tensor>
normParamGradients(Graph &graph, const Tensor &actsWhitened,
                   const Tensor &gradsIn, float scale, Sequence &prog,
                   const Type &partialsType, bool attemptRegroup,
                   const DebugNameAndId &dnai) {
  const std::string layer = "Norm/deltas";
  logging::poplin::info(
      "normParamGradients actsWhitened={}, gradsIn={}, scale={}, "
      "type={}, attemptRegroup={}, name={}",
      actsWhitened.shape(), gradsIn.shape(), scale, partialsType,
      attemptRegroup, dnai.getPathName() + "/" + layer);

  auto gradsInMaybeRegrouped =
      attemptRegroup ? popops::rearrange::regroupIfBeneficial(
                           graph, gradsIn, actsWhitened, prog, {dnai})
                     : gradsIn;
  const auto gradsInMultActs =
      mul(graph, actsWhitened, gradsInMaybeRegrouped, prog, {dnai, layer});

  auto numChannels = gradsInMultActs.dim(1);
  const auto concatInputs = concat({gradsInMultActs, gradsInMaybeRegrouped}, 1);

  std::vector<ComputeSet> css;

  // For beta = Re{gradsIn} where Re{x} reduces the tensor x along the
  //                              second dimension to produce a vector
  //                              of length x.dim(1)
  // For gamma = Re{actsWhitened .* gradsIn}
  //                              .* is element-wise multiplication operator
  //                              Reduction along second dimension

  auto scaleTensor = graph.addConstant(FLOAT, {}, scale, {dnai, "scaleTensor"});
  graph.setTileMapping(scaleTensor, 0);
  const auto concatDeltas =
      normReduce(graph, concatInputs, scaleTensor, false, css, partialsType,
                 gradsInMaybeRegrouped.elementType(), nullptr,
                 {dnai, layer + "/JointGammaDelta"});

  for (const auto &cs : css) {
    prog.add(Execute(cs, {dnai}));
  }

  return std::make_pair(concatDeltas.slice(0, numChannels),
                        concatDeltas.slice(numChannels, 2 * numChannels));
}

std::pair<Tensor, Tensor>
normParamGradients(Graph &graph, const Tensor &actsWhitened,
                   const Tensor &gradsIn, Sequence &prog,
                   const Type &partialsType,
                   const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext,
                                 DI_ARGS(actsWhitened, gradsIn, partialsType));

  auto outputs = normParamGradients(graph, actsWhitened, gradsIn, 1.0, prog,
                                    partialsType, true, {di});
  di.addOutputs({{"varGrad", toProfileValue(outputs.first)},
                 {"meanGrad", toProfileValue(outputs.second)}});
  return outputs;
}

Tensor normGradients(Graph &graph, const Tensor &gradsIn, const Tensor &gamma,
                     Sequence &prog, const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(debugContext, DI_ARGS(gradsIn, gamma));

  const auto layer = "NormGrad";
  logging::poplin::info("normGradients gradsIn={}, gamma={}, name={}",
                        gradsIn.shape(), gamma.shape(),
                        debugContext.getPathName() + "/" + layer);
  auto gammaBroadcast = broadcastChannelToMatch(gradsIn, gamma);
  auto output = mul(graph, gradsIn, gammaBroadcast, prog, {di, layer});
  di.addOutput(output);
  return output;
}

Tensor normStatisticsGradients(Graph &graph, const Tensor &actsWhitened,
                               const Tensor &gradsIn, const Tensor &invStdDev,
                               Sequence &prog,
                               const Type &partialsType, // currently unused
                               const poplar::DebugContext &debugContext) {
  poputil::PoplibsOpDebugInfo di(
      debugContext, DI_ARGS(actsWhitened, gradsIn, invStdDev, partialsType));

  const std::string layer = "Norm/gradients";
  logging::poplin::info("normStatisticsGradients actsWhitened={}, gradsIn={}, "
                        "invStdDev={}, name={}",
                        actsWhitened.shape(), gradsIn.shape(),
                        invStdDev.shape(),
                        debugContext.getPathName() + "/" + layer);

  const auto actsShape = actsWhitened.shape();
  const auto numElements = actsWhitened.numElements() / actsWhitened.dim(1);
  const float rScale = 1.0f / numElements;

  auto gradsInMaybeRegrouped = popops::rearrange::regroupIfBeneficial(
      graph, gradsIn, actsWhitened, prog, {di});

  // split rScale = rScale1 * rScale2;
  // TODO: T12898 Research what the optimal split would be dependent on model
  // and field size.
  const auto scaleSplit = 3.0f / 4;
  float rScale1 = std::pow(rScale, scaleSplit);
  float rScale2 = rScale / rScale1;
  const auto dType = actsWhitened.elementType();

  // If type is half, ensure that rScale2 is exactly representable in device
  // HALF type so that the fastest codelet is picked up when rScale2 is used
  // in the scaledAddTo below.
  if (dType == HALF) {
    rScale2 = castToDeviceHalfValue(graph.getTarget(), rScale2);
    // re-evaluate to get better combined precision
    rScale1 = rScale / rScale2;
  }
  Tensor varDelta, meanDelta;
  // See Description of Re{} operator in normParamGradients
  // varDelta = Re{actsWhitened .* gradsIn} * -rScale
  //   Size of varDelta is the size of inverse standard deviation
  // meanDelta = Re{gradsIn} * -rScale
  std::tie(varDelta, meanDelta) =
      normParamGradients(graph, actsWhitened, gradsInMaybeRegrouped, -rScale1,
                         prog, partialsType, false, {di});

  auto gradient = graph.clone(actsWhitened, {di, layer + "/gradsIn"});
  prog.add(Copy(gradsInMaybeRegrouped, gradient, false, {di}));

  // gradOut = gradsIn - rScale * actsWhitened .* Br{varDelta}
  // where Br{x} broadcast x along all dimensions other than dim(1) of
  // actsWhitened
  // gradsOut = gradsIn - rScale * actsWhitened .* Br{varDelta} + Br{meanDelta}

  auto varDeltaBroadcast = broadcastChannelToMatch(actsWhitened, varDelta);
  auto varGrads = mul(graph, actsWhitened, varDeltaBroadcast, prog,
                      {di, layer + "/varGrads"});
  mulInPlace(graph, meanDelta, rScale2, prog, {di, layer + "/scaleMeanDelta"});
  auto meanDeltaBroadcast = broadcastChannelToMatch(gradient, meanDelta);
  addInPlace(graph, gradient, meanDeltaBroadcast, prog,
             {di, layer + "/meanGrads"});
  // TODO: T12899 Once scaledAddTo is targeted efficiently in element-wise ops,
  // this should become a mapInPlace() expression.
  scaledAddTo(graph, gradient, varGrads, rScale2, prog,
              {di, layer + "/addGrads"});

  // Br{invStdDev} .* (gradsIn - rScale * actsWhitened .* Br{varDelta}
  //                   + Br{meanDelta})
  auto invStdDevBroadcast = broadcastChannelToMatch(gradient, invStdDev);
  mulInPlace(graph, gradient, invStdDevBroadcast, prog, {di, layer});
  di.addOutput(gradient);
  return gradient;
}

} // namespace poplin


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

// File generated at Thu 10 May 2018 14:30:15

/**
 * @file MSSMatMGUT_mAmu_two_scale_ewsb_solver.hpp
 *
 * @brief contains class for solving EWSB when two-scale algorithm is used
 *
 * This file was generated at Thu 10 May 2018 14:30:15 with FlexibleSUSY
 * 2.0.1 (git commit: unknown) and SARAH 4.12.2 .
 */

#ifndef MSSMatMGUT_mAmu_TWO_SCALE_EWSB_SOLVER_H
#define MSSMatMGUT_mAmu_TWO_SCALE_EWSB_SOLVER_H

#include "MSSMatMGUT_mAmu_ewsb_solver.hpp"
#include "MSSMatMGUT_mAmu_ewsb_solver_interface.hpp"
#include "error.hpp"

#include <Eigen/Core>

namespace flexiblesusy {

class EWSB_solver;
class Two_scale;

class MSSMatMGUT_mAmu_mass_eigenstates;

template<>
class MSSMatMGUT_mAmu_ewsb_solver<Two_scale> : public MSSMatMGUT_mAmu_ewsb_solver_interface {
public:
   MSSMatMGUT_mAmu_ewsb_solver() = default;
   MSSMatMGUT_mAmu_ewsb_solver(const MSSMatMGUT_mAmu_ewsb_solver&) = default;
   MSSMatMGUT_mAmu_ewsb_solver(MSSMatMGUT_mAmu_ewsb_solver&&) = default;
   virtual ~MSSMatMGUT_mAmu_ewsb_solver() {}
   MSSMatMGUT_mAmu_ewsb_solver& operator=(const MSSMatMGUT_mAmu_ewsb_solver&) = default;
   MSSMatMGUT_mAmu_ewsb_solver& operator=(MSSMatMGUT_mAmu_ewsb_solver&&) = default;

   virtual void set_loop_order(int l) override { loop_order = l; }
   virtual void set_number_of_iterations(int n) override { number_of_iterations = n; }
   virtual void set_precision(double p) override { precision = p; }

   virtual int get_loop_order() const override { return loop_order; }
   virtual int get_number_of_iterations() const override { return number_of_iterations; }
   virtual double get_precision() const override { return precision; }

   virtual int solve(MSSMatMGUT_mAmu_mass_eigenstates&) override;
private:
   static const int number_of_ewsb_equations = 2;
   using EWSB_vector_t = Eigen::Matrix<double,number_of_ewsb_equations,1>;

   class EEWSBStepFailed : public Error {
   public:
      virtual ~EEWSBStepFailed() {}
      virtual std::string what() const { return "Could not perform EWSB step."; }
   };

   int number_of_iterations{100}; ///< maximum number of iterations
   int loop_order{2};             ///< loop order to solve EWSB at
   double precision{1.e-5};       ///< precision goal

   void set_ewsb_solution(MSSMatMGUT_mAmu_mass_eigenstates&, const EWSB_solver*);
   template <typename It> void set_best_ewsb_solution(MSSMatMGUT_mAmu_mass_eigenstates&, It, It);

   int solve_tree_level(MSSMatMGUT_mAmu_mass_eigenstates&);
   int solve_iteratively(MSSMatMGUT_mAmu_mass_eigenstates&);
   int solve_iteratively_at(MSSMatMGUT_mAmu_mass_eigenstates&, int);
   int solve_iteratively_with(MSSMatMGUT_mAmu_mass_eigenstates&, EWSB_solver*, const EWSB_vector_t&);

   EWSB_vector_t initial_guess(const MSSMatMGUT_mAmu_mass_eigenstates&) const;
   EWSB_vector_t tadpole_equations(const MSSMatMGUT_mAmu_mass_eigenstates&) const;
   EWSB_vector_t ewsb_step(const MSSMatMGUT_mAmu_mass_eigenstates&) const;
};

} // namespace flexiblesusy

#endif


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

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

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, 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)

// 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_PROJECTS_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IMPL_PROJECTS_HPP

#include <cstring>
#include <string>
#include <vector>

#include <boost/geometry/srs/projections/exception.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_pod.hpp>

namespace boost { namespace geometry { namespace projections
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

/* some useful constants */
template <typename T>
inline T ONEPI() { return boost::math::constants::pi<T>(); }
template <typename T>
inline T HALFPI() { return boost::math::constants::half_pi<T>(); }
template <typename T>
inline T FORTPI() { return boost::math::constants::pi<T>() / T(4); }
template <typename T>
inline T TWOPI() { return boost::math::constants::two_pi<T>(); }
template <typename T>
inline T TWO_D_PI() { return boost::math::constants::two_div_pi<T>(); }
template <typename T>
inline T HALFPI_SQR() { return 2.4674011002723396547086227499689; }
template <typename T>
inline T PI_SQR() { return boost::math::constants::pi_sqr<T>(); }
template <typename T>
inline T THIRD() { return 0.3333333333333333333333333333333; }
template <typename T>
inline T TWOTHIRD() { return 0.6666666666666666666666666666666; }
template <typename T>
inline T PI_HALFPI() { return 4.7123889803846898576939650749193; }
template <typename T>
inline T TWOPI_HALFPI() { return 7.8539816339744830961566084581988; }
template <typename T>
inline T PI_DIV_3() { return 1.0471975511965977461542144610932; }

/* datum_type values */
static const int PJD_UNKNOWN = 0;
static const int PJD_3PARAM = 1;
static const int PJD_7PARAM = 2;
static const int PJD_GRIDSHIFT = 3;
static const int PJD_WGS84 = 4;   /* WGS84 (or anything considered equivelent) */

/* library errors */
static const int PJD_ERR_GEOCENTRIC = -45;
static const int PJD_ERR_AXIS = -47;
static const int PJD_ERR_GRID_AREA = -48;
static const int PJD_ERR_CATALOG = -49;

template <typename T>
struct pvalue
{
    std::string param;
    int used;

    int i;
    T f;
    std::string s;
};

template <typename T>
struct pj_const_pod
{
    int over;   /* over-range flag */
    int geoc;   /* geocentric latitude flag */
    int is_latlong; /* proj=latlong ... not really a projection at all */
    int is_geocent; /* proj=geocent ... not really a projection at all */
    T
        a,  /* major axis or radius if es==0 */
        a_orig, /* major axis before any +proj related adjustment */
        es, /* e ^ 2 */
        es_orig, /* es before any +proj related adjustment */
        e,  /* eccentricity */
        ra, /* 1/A */
        one_es, /* 1 - e^2 */
        rone_es, /* 1/one_es */
        lam0, phi0, /* central longitude, latitude */
        x0, y0, /* easting and northing */
        k0,    /* general scaling factor */
        to_meter, fr_meter, /* cartesian scaling */
        vto_meter, vfr_meter;      /* Vertical scaling. Internal unit [m] */

    int datum_type; /* PJD_UNKNOWN/3PARAM/7PARAM/GRIDSHIFT/WGS84 */
    T  datum_params[7];
    T  from_greenwich; /* prime meridian offset (in radians) */
    T  long_wrap_center; /* 0.0 for -180 to 180, actually in radians*/
    bool    is_long_wrap_set;

    // Initialize all variables to zero
    pj_const_pod()
    {
        std::memset(this, 0, sizeof(pj_const_pod));
    }
};

template <typename T>
struct pj_const_non_pod
{
    int over;   /* over-range flag */
    int geoc;   /* geocentric latitude flag */
    int is_latlong; /* proj=latlong ... not really a projection at all */
    int is_geocent; /* proj=geocent ... not really a projection at all */
    T
        a,  /* major axis or radius if es==0 */
        a_orig, /* major axis before any +proj related adjustment */
        es, /* e ^ 2 */
        es_orig, /* es before any +proj related adjustment */
        e,  /* eccentricity */
        ra, /* 1/A */
        one_es, /* 1 - e^2 */
        rone_es, /* 1/one_es */
        lam0, phi0, /* central longitude, latitude */
        x0, y0, /* easting and northing */
        k0,    /* general scaling factor */
        to_meter, fr_meter, /* cartesian scaling */
        vto_meter, vfr_meter;      /* Vertical scaling. Internal unit [m] */

    int datum_type; /* PJD_UNKNOWN/3PARAM/7PARAM/GRIDSHIFT/WGS84 */
    T  datum_params[7];
    T  from_greenwich; /* prime meridian offset (in radians) */
    T  long_wrap_center; /* 0.0 for -180 to 180, actually in radians*/
    bool    is_long_wrap_set;

    // Initialize all variables to zero
    pj_const_non_pod()
        : over(0), geoc(0), is_latlong(0), is_geocent(0)
        , a(0), a_orig(0), es(0), es_orig(0), e(0), ra(0)
        , one_es(0), rone_es(0), lam0(0), phi0(0), x0(0), y0(0), k0(0)
        , to_meter(0), fr_meter(0), vto_meter(0), vfr_meter(0)
        , datum_type(PJD_UNKNOWN)
        , from_greenwich(0), long_wrap_center(0), is_long_wrap_set(false)
    {
        datum_params[0] = 0;
        datum_params[1] = 0;
        datum_params[2] = 0;
        datum_params[3] = 0;
        datum_params[4] = 0;
        datum_params[5] = 0;
        datum_params[6] = 0;
    }
};

template <typename T>
struct pj_const
    : boost::mpl::if_c
        <
            boost::is_pod<T>::value,
            pj_const_pod<T>,
            pj_const_non_pod<T>
        >::type
{};

// PROJ4 complex. Might be replaced with std::complex
template <typename T>
struct COMPLEX { T r, i; };

struct PJ_ELLPS
{
    std::string id;    /* ellipse keyword name */
    std::string major;    /* a= value */
    std::string ell;    /* elliptical parameter */
    std::string name;    /* comments */
};

struct PJ_DATUMS
{
    std::string id;     /* datum keyword */
    std::string defn;   /* ie. "to_wgs84=..." */
    std::string ellipse_id; /* ie from ellipse table */
    std::string comments; /* EPSG code, etc */
};

struct PJ_PRIME_MERIDIANS
{
    std::string id;     /* prime meridian keyword */
    std::string defn;   /* offset from greenwich in DMS format. */
};

struct PJ_UNITS
{
    std::string id;    /* units keyword */
    std::string to_meter;    /* multiply by value to get meters */
    std::string name;    /* comments */
};

template <typename T>
struct DERIVS
{
    T x_l, x_p; /* derivatives of x for lambda-phi */
    T y_l, y_p; /* derivatives of y for lambda-phi */
};

template <typename T>
struct FACTORS
{
    DERIVS<T> der;
    T h, k;    /* meridinal, parallel scales */
    T omega, thetap;    /* angular distortion, theta prime */
    T conv;    /* convergence */
    T s;        /* areal scale factor */
    T a, b;    /* max-min scale error */
    int code;        /* info as to analytics, see following */
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

/*!
    \brief parameters, projection parameters
    \details This structure initializes all projections
    \ingroup projection
*/
template <typename T>
struct parameters : public detail::pj_const<T>
{
    typedef T type;

    std::string name;
    std::vector<detail::pvalue<T> > params;
};

}}} // namespace boost::geometry::projections
#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PROJECTS_HPP