//  Copyright (c) 2013 Christopher Kormanyos
//  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)
//
// This work is based on an earlier work:
// "Algorithm 910: A Portable C++ Multiple-Precision System for Special-Function Calculations",
// in ACM TOMS, {VOL 37, ISSUE 4, (February 2011)} (C) ACM, 2011. http://doi.acm.org/10.1145/1916461.1916469
//
// This header contains implementation details for estimating the zeros
// of cylindrical Bessel and Neumann functions on the positive real axis.
// Support is included for both positive as well as negative order.
// Various methods are used to estimate the roots. These include
// empirical curve fitting and McMahon's asymptotic approximation
// for small order, uniform asymptotic expansion for large order,
// and iteration and root interlacing for negative order.
//
#ifndef _BESSEL_JY_ZERO_2013_01_18_HPP_
  #define _BESSEL_JY_ZERO_2013_01_18_HPP_

  #include <algorithm>
  #include <boost/math/constants/constants.hpp>
  #include <boost/math/special_functions/math_fwd.hpp>
  #include <boost/math/special_functions/cbrt.hpp>
  #include <boost/math/special_functions/detail/airy_ai_bi_zero.hpp>

  namespace boost { namespace math {
  namespace detail
  {
    namespace bessel_zero
    {
      template<class T>
      T equation_nist_10_21_19(const T& v, const T& a)
      {
        // Get the initial estimate of the m'th root of Jv or Yv.
        // This subroutine is used for the order m with m > 1.
        // The order m has been used to create the input parameter a.

        // This is Eq. 10.21.19 in the NIST Handbook.
        const T mu                  = (v * v) * 4U;
        const T mu_minus_one        = mu - T(1);
        const T eight_a_inv         = T(1) / (a * 8U);
        const T eight_a_inv_squared = eight_a_inv * eight_a_inv;

        const T term3 = ((mu_minus_one *  4U) *     ((mu *    7U) -     T(31U) )) / 3U;
        const T term5 = ((mu_minus_one * 32U) *   ((((mu *   83U) -    T(982U) ) * mu) +    T(3779U) )) / 15U;
        const T term7 = ((mu_minus_one * 64U) * ((((((mu * 6949U) - T(153855UL)) * mu) + T(1585743UL)) * mu) - T(6277237UL))) / 105U;

        return a + ((((                      - term7
                       * eight_a_inv_squared - term5)
                       * eight_a_inv_squared - term3)
                       * eight_a_inv_squared - mu_minus_one)
                       * eight_a_inv);
      }

      template<typename T>
      class equation_as_9_3_39_and_its_derivative
      {
      public:
        equation_as_9_3_39_and_its_derivative(const T& zt) : zeta(zt) { }

        boost::math::tuple<T, T> operator()(const T& z) const
        {
          BOOST_MATH_STD_USING // ADL of std names, needed for acos, sqrt.

          // Return the function of zeta that is implicitly defined
          // in A&S Eq. 9.3.39 as a function of z. The function is
          // returned along with its derivative with respect to z.

          const T zsq_minus_one_sqrt = sqrt((z * z) - T(1));

          const T the_function(
              zsq_minus_one_sqrt
            - (  acos(T(1) / z) + ((T(2) / 3U) * (zeta * sqrt(zeta)))));

          const T its_derivative(zsq_minus_one_sqrt / z);

          return boost::math::tuple<T, T>(the_function, its_derivative);
        }

      private:
        const equation_as_9_3_39_and_its_derivative& operator=(const equation_as_9_3_39_and_its_derivative&);
        const T zeta;
      };

      template<class T>
      static T equation_as_9_5_26(const T& v, const T& ai_bi_root)
      {
        BOOST_MATH_STD_USING // ADL of std names, needed for log, sqrt.

        // Obtain the estimate of the m'th zero of Jv or Yv.
        // The order m has been used to create the input parameter ai_bi_root.
        // Here, v is larger than about 2.2. The estimate is computed
        // from Abramowitz and Stegun Eqs. 9.5.22 and 9.5.26, page 371.
        //
        // The inversion of z as a function of zeta is mentioned in the text
        // following A&S Eq. 9.5.26. Here, we accomplish the inversion by
        // performing a Taylor expansion of Eq. 9.3.39 for large z to order 2
        // and solving the resulting quadratic equation, thereby taking
        // the positive root of the quadratic.
        // In other words: (2/3)(-zeta)^(3/2) approx = z + 1/(2z) - pi/2.
        // This leads to: z^2 - [(2/3)(-zeta)^(3/2) + pi/2]z + 1/2 = 0.
        //
        // With this initial estimate, Newton-Raphson iteration is used
        // to refine the value of the estimate of the root of z
        // as a function of zeta.

        const T v_pow_third(boost::math::cbrt(v));
        const T v_pow_minus_two_thirds(T(1) / (v_pow_third * v_pow_third));

        // Obtain zeta using the order v combined with the m'th root of
        // an airy function, as shown in  A&S Eq. 9.5.22.
        const T zeta = v_pow_minus_two_thirds * (-ai_bi_root);

        const T zeta_sqrt = sqrt(zeta);

        // Set up a quadratic equation based on the Taylor series
        // expansion mentioned above.
        const T b = -((((zeta * zeta_sqrt) * 2U) / 3U) + boost::math::constants::half_pi<T>());

        // Solve the quadratic equation, taking the positive root.
        const T z_estimate = (-b + sqrt((b * b) - T(2))) / 2U;

        // Establish the range, the digits, and the iteration limit
        // for the upcoming root-finding.
        const T range_zmin = (std::max<T>)(z_estimate - T(1), T(1));
        const T range_zmax = z_estimate + T(1);

        const int my_digits10 = static_cast<int>(static_cast<float>(boost::math::tools::digits<T>() * 0.301F));

        // Select the maximum allowed iterations based on the number
        // of decimal digits in the numeric type T, being at least 12.
        const boost::uintmax_t iterations_allowed = static_cast<boost::uintmax_t>((std::max)(12, my_digits10 * 2));

        boost::uintmax_t iterations_used = iterations_allowed;

        // Calculate the root of z as a function of zeta.
        const T z = boost::math::tools::newton_raphson_iterate(
          boost::math::detail::bessel_zero::equation_as_9_3_39_and_its_derivative<T>(zeta),
          z_estimate,
          range_zmin,
          range_zmax,
          (std::min)(boost::math::tools::digits<T>(), boost::math::tools::digits<float>()),
          iterations_used);

        static_cast<void>(iterations_used);

        // Continue with the implementation of A&S Eq. 9.3.39.
        const T zsq_minus_one      = (z * z) - T(1);
        const T zsq_minus_one_sqrt = sqrt(zsq_minus_one);

        // This is A&S Eq. 9.3.42.
        const T b0_term_5_24 = T(5) / ((zsq_minus_one * zsq_minus_one_sqrt) * 24U);
        const T b0_term_1_8  = T(1) / ( zsq_minus_one_sqrt * 8U);
        const T b0_term_5_48 = T(5) / ((zeta * zeta) * 48U);

        const T b0 = -b0_term_5_48 + ((b0_term_5_24 + b0_term_1_8) / zeta_sqrt);

        // This is the second line of A&S Eq. 9.5.26 for f_k with k = 1.
        const T f1 = ((z * zeta_sqrt) * b0) / zsq_minus_one_sqrt;

        // This is A&S Eq. 9.5.22 expanded to k = 1 (i.e., one term in the series).
        return (v * z) + (f1 / v);
      }

      namespace cyl_bessel_j_zero_detail
      {
        template<class T>
        T equation_nist_10_21_40_a(const T& v)
        {
          const T v_pow_third(boost::math::cbrt(v));
          const T v_pow_minus_two_thirds(T(1) / (v_pow_third * v_pow_third));

          return v * (((((                         + T(0.043)
                          * v_pow_minus_two_thirds - T(0.0908))
                          * v_pow_minus_two_thirds - T(0.00397))
                          * v_pow_minus_two_thirds + T(1.033150))
                          * v_pow_minus_two_thirds + T(1.8557571))
                          * v_pow_minus_two_thirds + T(1));
        }

        template<class T, class Policy>
        class function_object_jv
        {
        public:
          function_object_jv(const T& v,
                             const Policy& pol) : my_v(v),
                                                  my_pol(pol) { }

          T operator()(const T& x) const
          {
            return boost::math::cyl_bessel_j(my_v, x, my_pol);
          }

        private:
          const T my_v;
          const Policy& my_pol;
          const function_object_jv& operator=(const function_object_jv&);
        };

        template<class T, class Policy>
        class function_object_jv_and_jv_prime
        {
        public:
          function_object_jv_and_jv_prime(const T& v,
                                          const bool order_is_zero,
                                          const Policy& pol) : my_v(v),
                                                               my_order_is_zero(order_is_zero),
                                                               my_pol(pol) { }

          boost::math::tuple<T, T> operator()(const T& x) const
          {
            // Obtain Jv(x) and Jv'(x).
            // Chris's original code called the Bessel function implementation layer direct,
            // but that circumvented optimizations for integer-orders.  Call the documented
            // top level functions instead, and let them sort out which implementation to use.
            T j_v;
            T j_v_prime;

            if(my_order_is_zero)
            {
              j_v       =  boost::math::cyl_bessel_j(0, x, my_pol);
              j_v_prime = -boost::math::cyl_bessel_j(1, x, my_pol);
            }
            else
            {
                      j_v       = boost::math::cyl_bessel_j(  my_v,      x, my_pol);
              const T j_v_m1     (boost::math::cyl_bessel_j(T(my_v - 1), x, my_pol));
                      j_v_prime = j_v_m1 - ((my_v * j_v) / x);
            }

            // Return a tuple containing both Jv(x) and Jv'(x).
            return boost::math::make_tuple(j_v, j_v_prime);
          }

        private:
          const T my_v;
          const bool my_order_is_zero;
          const Policy& my_pol;
          const function_object_jv_and_jv_prime& operator=(const function_object_jv_and_jv_prime&);
        };

        template<class T> bool my_bisection_unreachable_tolerance(const T&, const T&) { return false; }

        template<class T, class Policy>
        T initial_guess(const T& v, const int m, const Policy& pol)
        {
          BOOST_MATH_STD_USING // ADL of std names, needed for floor.

          // Compute an estimate of the m'th root of cyl_bessel_j.

          T guess;

          // There is special handling for negative order.
          if(v < 0)
          {
            if((m == 1) && (v > -0.5F))
            {
              // For small, negative v, use the results of empirical curve fitting.
              // Mathematica(R) session for the coefficients:
              //  Table[{n, BesselJZero[n, 1]}, {n, -(1/2), 0, 1/10}]
              //  N[%, 20]
              //  Fit[%, {n^0, n^1, n^2, n^3, n^4, n^5, n^6}, n]
              guess = (((((    - T(0.2321156900729)
                           * v - T(0.1493247777488))
                           * v - T(0.15205419167239))
                           * v + T(0.07814930561249))
                           * v - T(0.17757573537688))
                           * v + T(1.542805677045663))
                           * v + T(2.40482555769577277);

              return guess;
            }

            // Create the positive order and extract its positive floor integer part.
            const T vv(-v);
            const T vv_floor(floor(vv));

            // The to-be-found root is bracketed by the roots of the
            // Bessel function whose reflected, positive integer order
            // is less than, but nearest to vv.

            T root_hi = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::initial_guess(vv_floor, m, pol);
            T root_lo;

            if(m == 1)
            {
              // The estimate of the first root for negative order is found using
              // an adaptive range-searching algorithm.
              root_lo = T(root_hi - 0.1F);

              const bool hi_end_of_bracket_is_negative = (boost::math::cyl_bessel_j(v, root_hi, pol) < 0);

              while((root_lo > boost::math::tools::epsilon<T>()))
              {
                const bool lo_end_of_bracket_is_negative = (boost::math::cyl_bessel_j(v, root_lo, pol) < 0);

                if(hi_end_of_bracket_is_negative != lo_end_of_bracket_is_negative)
                {
                  break;
                }

                root_hi = root_lo;

                // Decrease the lower end of the bracket using an adaptive algorithm.
                if(root_lo > 0.5F)
                {
                  root_lo -= 0.5F;
                }
                else
                {
                  root_lo *= 0.75F;
                }
              }
            }
            else
            {
              root_lo = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::initial_guess(vv_floor, m - 1, pol);
            }

            // Perform several steps of bisection iteration to refine the guess.
            boost::uintmax_t number_of_iterations(12U);

            // Do the bisection iteration.
            const boost::math::tuple<T, T> guess_pair =
               boost::math::tools::bisect(
                  boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::function_object_jv<T, Policy>(v, pol),
                  root_lo,
                  root_hi,
                  boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::my_bisection_unreachable_tolerance<T>,
                  number_of_iterations);

            return (boost::math::get<0>(guess_pair) + boost::math::get<1>(guess_pair)) / 2U;
          }

          if(m == 1U)
          {
            // Get the initial estimate of the first root.

            if(v < 2.2F)
            {
              // For small v, use the results of empirical curve fitting.
              // Mathematica(R) session for the coefficients:
              //  Table[{n, BesselJZero[n, 1]}, {n, 0, 22/10, 1/10}]
              //  N[%, 20]
              //  Fit[%, {n^0, n^1, n^2, n^3, n^4, n^5, n^6}, n]
              guess = (((((    - T(0.0008342379046010)
                           * v + T(0.007590035637410))
                           * v - T(0.030640914772013))
                           * v + T(0.078232088020106))
                           * v - T(0.169668712590620))
                           * v + T(1.542187960073750))
                           * v + T(2.4048359915254634);
            }
            else
            {
              // For larger v, use the first line of Eqs. 10.21.40 in the NIST Handbook.
              guess = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::equation_nist_10_21_40_a(v);
            }
          }
          else
          {
            if(v < 2.2F)
            {
              // Use Eq. 10.21.19 in the NIST Handbook.
              const T a(((v + T(m * 2U)) - T(0.5)) * boost::math::constants::half_pi<T>());

              guess = boost::math::detail::bessel_zero::equation_nist_10_21_19(v, a);
            }
            else
            {
              // Get an estimate of the m'th root of airy_ai.
              const T airy_ai_root(boost::math::detail::airy_zero::airy_ai_zero_detail::initial_guess<T>(m));

              // Use Eq. 9.5.26 in the A&S Handbook.
              guess = boost::math::detail::bessel_zero::equation_as_9_5_26(v, airy_ai_root);
            }
          }

          return guess;
        }
      } // namespace cyl_bessel_j_zero_detail

      namespace cyl_neumann_zero_detail
      {
        template<class T>
        T equation_nist_10_21_40_b(const T& v)
        {
          const T v_pow_third(boost::math::cbrt(v));
          const T v_pow_minus_two_thirds(T(1) / (v_pow_third * v_pow_third));

          return v * (((((                         - T(0.001)
                          * v_pow_minus_two_thirds - T(0.0060))
                          * v_pow_minus_two_thirds + T(0.01198))
                          * v_pow_minus_two_thirds + T(0.260351))
                          * v_pow_minus_two_thirds + T(0.9315768))
                          * v_pow_minus_two_thirds + T(1));
        }

        template<class T, class Policy>
        class function_object_yv
        {
        public:
          function_object_yv(const T& v,
                             const Policy& pol) : my_v(v),
                                                  my_pol(pol) { }

          T operator()(const T& x) const
          {
            return boost::math::cyl_neumann(my_v, x, my_pol);
          }

        private:
          const T my_v;
          const Policy& my_pol;
          const function_object_yv& operator=(const function_object_yv&);
        };

        template<class T, class Policy>
        class function_object_yv_and_yv_prime
        {
        public:
          function_object_yv_and_yv_prime(const T& v,
                                          const Policy& pol) : my_v(v),
                                                               my_pol(pol) { }

          boost::math::tuple<T, T> operator()(const T& x) const
          {
            const T half_epsilon(boost::math::tools::epsilon<T>() / 2U);

            const bool order_is_zero = ((my_v > -half_epsilon) && (my_v < +half_epsilon));

            // Obtain Yv(x) and Yv'(x).
            // Chris's original code called the Bessel function implementation layer direct,
            // but that circumvented optimizations for integer-orders.  Call the documented
            // top level functions instead, and let them sort out which implementation to use.
            T y_v;
            T y_v_prime;

            if(order_is_zero)
            {
              y_v       =  boost::math::cyl_neumann(0, x, my_pol);
              y_v_prime = -boost::math::cyl_neumann(1, x, my_pol);
            }
            else
            {
                      y_v       = boost::math::cyl_neumann(  my_v,      x, my_pol);
              const T y_v_m1     (boost::math::cyl_neumann(T(my_v - 1), x, my_pol));
                      y_v_prime = y_v_m1 - ((my_v * y_v) / x);
            }

            // Return a tuple containing both Yv(x) and Yv'(x).
            return boost::math::make_tuple(y_v, y_v_prime);
          }

        private:
          const T my_v;
          const Policy& my_pol;
          const function_object_yv_and_yv_prime& operator=(const function_object_yv_and_yv_prime&);
        };

        template<class T> bool my_bisection_unreachable_tolerance(const T&, const T&) { return false; }

        template<class T, class Policy>
        T initial_guess(const T& v, const int m, const Policy& pol)
        {
          BOOST_MATH_STD_USING // ADL of std names, needed for floor.

          // Compute an estimate of the m'th root of cyl_neumann.

          T guess;

          // There is special handling for negative order.
          if(v < 0)
          {
            // Create the positive order and extract its positive floor and ceiling integer parts.
            const T vv(-v);
            const T vv_floor(floor(vv));

            // The to-be-found root is bracketed by the roots of the
            // Bessel function whose reflected, positive integer order
            // is less than, but nearest to vv.

            // The special case of negative, half-integer order uses
            // the relation between Yv and spherical Bessel functions
            // in order to obtain the bracket for the root.
            // In these special cases, cyl_neumann(-n/2, x) = sph_bessel_j(+n/2, x)
            // for v = -n/2.

            T root_hi;
            T root_lo;

            if(m == 1)
            {
              // The estimate of the first root for negative order is found using
              // an adaptive range-searching algorithm.
              // Take special precautions for the discontinuity at negative,
              // half-integer orders and use different brackets above and below these.
              if(T(vv - vv_floor) < 0.5F)
              {
                root_hi = boost::math::detail::bessel_zero::cyl_neumann_zero_detail::initial_guess(vv_floor, m, pol);
              }
              else
              {
                root_hi = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::initial_guess(T(vv_floor + 0.5F), m, pol);
              }

              root_lo = T(root_hi - 0.1F);

              const bool hi_end_of_bracket_is_negative = (boost::math::cyl_neumann(v, root_hi, pol) < 0);

              while((root_lo > boost::math::tools::epsilon<T>()))
              {
                const bool lo_end_of_bracket_is_negative = (boost::math::cyl_neumann(v, root_lo, pol) < 0);

                if(hi_end_of_bracket_is_negative != lo_end_of_bracket_is_negative)
                {
                  break;
                }

                root_hi = root_lo;

                // Decrease the lower end of the bracket using an adaptive algorithm.
                if(root_lo > 0.5F)
                {
                  root_lo -= 0.5F;
                }
                else
                {
                  root_lo *= 0.75F;
                }
              }
            }
            else
            {
              if(T(vv - vv_floor) < 0.5F)
              {
                root_lo  = boost::math::detail::bessel_zero::cyl_neumann_zero_detail::initial_guess(vv_floor, m - 1, pol);
                root_hi = boost::math::detail::bessel_zero::cyl_neumann_zero_detail::initial_guess(vv_floor, m, pol);
                root_lo += 0.01F;
                root_hi += 0.01F;
              }
              else
              {
                root_lo = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::initial_guess(T(vv_floor + 0.5F), m - 1, pol);
                root_hi = boost::math::detail::bessel_zero::cyl_bessel_j_zero_detail::initial_guess(T(vv_floor + 0.5F), m, pol);
                root_lo += 0.01F;
                root_hi += 0.01F;
              }
            }

            // Perform several steps of bisection iteration to refine the guess.
            boost::uintmax_t number_of_iterations(12U);

            // Do the bisection iteration.
            const boost::math::tuple<T, T> guess_pair =
               boost::math::tools::bisect(
                  boost::math::detail::bessel_zero::cyl_neumann_zero_detail::function_object_yv<T, Policy>(v, pol),
                  root_lo,
                  root_hi,
                  boost::math::detail::bessel_zero::cyl_neumann_zero_detail::my_bisection_unreachable_tolerance<T>,
                  number_of_iterations);

            return (boost::math::get<0>(guess_pair) + boost::math::get<1>(guess_pair)) / 2U;
          }

          if(m == 1U)
          {
            // Get the initial estimate of the first root.

            if(v < 2.2F)
            {
              // For small v, use the results of empirical curve fitting.
              // Mathematica(R) session for the coefficients:
              //  Table[{n, BesselYZero[n, 1]}, {n, 0, 22/10, 1/10}]
              //  N[%, 20]
              //  Fit[%, {n^0, n^1, n^2, n^3, n^4, n^5, n^6}, n]
              guess = (((((    - T(0.0025095909235652)
                           * v + T(0.021291887049053))
                           * v - T(0.076487785486526))
                           * v + T(0.159110268115362))
                           * v - T(0.241681668765196))
                           * v + T(1.4437846310885244))
                           * v + T(0.89362115190200490);
            }
            else
            {
              // For larger v, use the second line of Eqs. 10.21.40 in the NIST Handbook.
              guess = boost::math::detail::bessel_zero::cyl_neumann_zero_detail::equation_nist_10_21_40_b(v);
            }
          }
          else
          {
            if(v < 2.2F)
            {
              // Use Eq. 10.21.19 in the NIST Handbook.
              const T a(((v + T(m * 2U)) - T(1.5)) * boost::math::constants::half_pi<T>());

              guess = boost::math::detail::bessel_zero::equation_nist_10_21_19(v, a);
            }
            else
            {
              // Get an estimate of the m'th root of airy_bi.
              const T airy_bi_root(boost::math::detail::airy_zero::airy_bi_zero_detail::initial_guess<T>(m));

              // Use Eq. 9.5.26 in the A&S Handbook.
              guess = boost::math::detail::bessel_zero::equation_as_9_5_26(v, airy_bi_root);
            }
          }

          return guess;
        }
      } // namespace cyl_neumann_zero_detail
    } // namespace bessel_zero
  } } } // namespace boost::math::detail

#endif // _BESSEL_JY_ZERO_2013_01_18_HPP_


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_CONSTANTS_DEGINRAD_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_CONSTANTS_DEGINRAD_HPP_INCLUDED
/*!
 * \file
**/
#include <boost/simd/sdk/constant/constant.hpp>
#include <boost/simd/sdk/constant/register.hpp>

/*!
 * \ingroup trigo_constant
 * \defgroup trigo_constant_deginrad Deginrad
 * \par Description
 * Constant Deginrad : radian in degree  multiplier, \f$\frac\pi{180}\f$.
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/deginrad.hpp>
 * \endcode
 *
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class T,class A0>
 *     meta::call<tag::_deginrad_(A0)>::type
 *     Deginrad();
 * }
 * \endcode
 *
 *
 * \param T template parameter of Deginrad
 *
 * \return type T value
 *
 *
**/

namespace nt2
{
  namespace tag
  {
    // 8.47842766036889956997e-32
    BOOST_SIMD_CONSTANT_REGISTER( Deginrad, double
                                , 0, 0x3c8efa35
                                , 0x3f91df46a2529d39ll
                                );
  }

  BOOST_SIMD_CONSTANT_IMPLEMENTATION(tag::Deginrad, Deginrad);

  static const long double long_deginrad = 0.017453292519943295769236907684886l;
}

#endif


/*!
 * @file
 * An example of logistic regression training and testing.
 * The data is taken from:
 *
 * command to run:
 * mpirung -n 4 ./bin/logreg "data/logreg/train.csv"
 *
 * For running on some different data-set specify the columns etc. in `fromFile`
 * Also change the `dim` parameter and inFile variable.
 * Testing data files can be given as arguments after training data file.
 *
 * benchmarks at the bottom
 * */
#include <array>
#include <iostream>
#include <stdexcept>
#include <boost/mpi.hpp>

#include <ezl.hpp>
#include <ezl/algorithms/io.hpp>
#include <ezl/algorithms/reduceAlls.hpp>
#include <ezl/algorithms/reduces.hpp>
#include <ezl/algorithms/fromFile.hpp>

using namespace std;

double sigmoid(double x) {
  constexpr auto e = 2.718281828;
  return 1.0 / (1.0 + pow(e, -x));
}

template <size_t dim>
double calcNorm(const array<double, dim> &weights,
                const array<double, dim> &weightsNew) {
  auto sum = 0.;
  for (size_t i = 0; i < weights.size(); ++i) {
    auto minus = weights[i] - weightsNew[i];
    sum += (minus * minus);
  }
  return sqrt(sum);
}

template <size_t dim>
auto calcGrad(const double &y, const array<double, dim> &x,
                const array<double, dim> &w) {
  array<double, dim> grad;
  auto dot = std::inner_product(::begin(w), ::end(w), ::begin(x), 0);
  //auto s = (sigmoid(y * dot) - 1) * y;
  auto s = sigmoid(dot) - y;
  for (size_t i = 0; i < w.size(); ++i) {
    grad[i] = s * x[i];
  }
  return grad;
}

void logreg(int argc, char* argv[]) {
  if (argc < 2) {
    cerr << "Please provide arguments as glob pattern for train file(s), "
            "followed by test file pattern(s). Check source for defaults or "
            "for running on some other data-format.";
    return;
  }

  constexpr auto dim = 3;  // number of features
  constexpr auto maxIters = 1000;

  // specify columns and other read properties if required.
  auto reader =
      ezl::fromFile<double, array<double, dim>>(argv[1]).colSeparator(",");

  // load once in memory
  auto data = ezl::rise(reader)
                  .runResult();

  if (data.empty()) {
    cout<<"no data";
    return;
  }

  auto sumArray = [](auto &a, auto &b) -> auto & {
    transform(begin(a), end(a), begin(b), begin(b), plus<double>());
    return b;
  };

  array<double, dim> w{};  // weights initialised to zero;
  // build flow for final gradient value in all procs
  auto train = ezl::rise(ezl::fromMem(data))
                   .map([&w](auto& y, auto& x) {
                     return calcGrad(y, x, w);
                   }).colsTransform()
                   .reduce(sumArray, array<double, dim>{}).inprocess()
                   .reduce(sumArray, array<double, dim>{})
                     .prll(1., ezl::llmode::task | ezl::llmode::all)
                   .build();

  auto iters = 0;
  auto norm = 0.;
  while (iters++ < maxIters) {
    array<double, dim> wn, grad;
    tie(grad) =  ezl::flow(train).runResult()[0]; // running flow
    constexpr static auto gamma = 0.002;
    transform(begin(w), end(w), begin(grad), begin(wn),
                   [](double a, double b) { return a - gamma * b;});
    norm = calcNorm(wn, w);
    w = move(wn);
    constexpr auto epsilon = 0.0001;
    if(norm < epsilon)  break;
  }
  cout<<"iterations: "<<iters-1<<endl;  // TODO: message
  cout<<"norm: "<<norm<<endl;
  cout<<"final weights: "<<w<<endl;

  // building testing flow
  auto testFlow = ezl::rise(reader)
                      .map<2>([&w](auto x) {
                        auto pred = 0.;
                        for (size_t i = 0; i < get<0>(x).size(); ++i) {
                          pred += w[i] * get<0>(x)[i];
                        }
                        return (sigmoid(pred) > 0.5);
                      }).colsTransform()
                      .reduce<1, 2>(ezl::count(), 0)
                        .dump("", "real-y, predicted-y, count")
                      .build();

  for (int i = 1; i < argc; ++i) {
    reader = reader.filePattern(argv[i]);
    cout<<"Testing for "<<argv[i]<<endl;
    ezl::flow(testFlow).run();
  }
}

int main(int argc, char *argv[]) {
  boost::mpi::environment env(argc, argv, false);
  try {
    logreg(argc, argv);
  } catch (const exception& ex) {
    cerr<<"error: "<<ex.what()<<'\n';
    env.abort(1);
  } catch (...) {
    cerr<<"unknown exception\n";
    env.abort(2);
  }
  return 0;
}

/*!
 * benchmark results: i7(hdd); input: 450MBs
 *  *nprocs* | 1   | 2   | 4    |
 *  ---      |---  |---  |---   |
 *  *time(s)*| 120 | 63  | 38   |
 *
 * benchmark results: Linux(nfs-3); input: 2.9GBs; units: secs
 *  *nprocs* | 1x12      | 2x12      | 4x12      | 8x12      |  12x12   |
 *  ---      |---        |---        |---        | ---       |          |
 *  *time(s)*| 190       | 91        | 50        | 36        |  34      |
 */


#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <thread>
#include <boost/math/special_functions/bessel.hpp>
#include "spida/transform/hankelR.h"
#include "spida/grid/besselR.h"
#if defined(HAVE_OPENBLAS)
    #include "cblas.h"
#endif

namespace spida {

  HankelTransformR::HankelTransformR(const BesselRootGridR& grid) :
      m_nr(grid.getNr()),
      m_Ymk(m_nr*m_nr),
      m_YmkC(m_nr*m_nr)
  {
      m_alpha = grid.getjN()/pow(grid.getMaxSR(),2);
      initDHT(grid);
  }

  void HankelTransformR::R_To_SR(const double* in,double* out)
  {
/*
enum CBLAS_ORDER {CblasRowMajor=101, CblasColMajor=102};
enum CBLAS_TRANSPOSE    {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113};
cblas_dgemv(const enum CBLAS_ORDER Order,
           const enum CBLAS_TRANSPOSE TransA, const int M, const int N,
           const double alpha, const double *A, const int lda,
           const double *X, const int incX, const double beta,
           double *Y, const int incY);
dgemv y = alpha*A*x + beta*y
lda -> first dimension of A
*/

      #if defined(HAVE_OPENBLAS)
      cblas_dgemv(CblasRowMajor,CblasNoTrans,m_nr,m_nr,m_alpha,m_Ymk.data(),m_nr,in,1,0.0,out,1);
      #else
      for(int m = 0; m < m_nr; m++){
          double sum = 0.0;
          for(int k = 0; k < m_nr; k++)
              sum += m_Ymk[m*m_nr+k]*in[k];
          out[m] = m_alpha*sum;
      }
      #endif
  }

  void HankelTransformR::R_To_SR(const dcmplx* in,dcmplx* out)
  {
      #if defined(HAVE_OPENBLAS)
      const double beta = 0.0;
      cblas_zgemv(CblasRowMajor,CblasNoTrans,m_nr,m_nr,&m_alpha,m_YmkC.data(),m_nr,in,1,&beta,out,1);
      #else
      for(int m = 0; m < m_nr; m++){
          dcmplx sum = 0.0;
          for(int k = 0; k < m_nr; k++)
              sum += m_YmkC[m*m_nr+k]*in[k];
          out[m] = m_alpha*sum;
      }
      #endif
  }

  void HankelTransformR::SR_To_R(const double* in,double* out)
  {
      #if defined(HAVE_OPENBLAS)
      cblas_dgemv(CblasRowMajor,CblasNoTrans,m_nr,m_nr,1.0/m_alpha,m_Ymk.data(),m_nr,in,1,0.0,out,1);
      #else
      for(int k = 0; k < m_nr; k++){
          double sum = 0.0;
          for(int m = 0; m < m_nr; m++)
              sum += m_Ymk[k*m_nr+m]*in[m];
          out[k] = sum/m_alpha;
      }
      #endif
  }

  void HankelTransformR::SR_To_R(const dcmplx* in,dcmplx* out)
  {
      #if defined(HAVE_OPENBLAS)
      const dcmplx a = 1.0/m_alpha;
      const dcmplx beta = 0.0;
      cblas_zgemv(CblasRowMajor,CblasNoTrans,m_nr,m_nr,&a,m_YmkC.data(),m_nr,in,1,&beta,out,1);
      #else
      for(int k = 0; k < m_nr; k++){
          dcmplx sum = 0.0;
          for(int m = 0; m < m_nr; m++)
              sum += m_YmkC[k*m_nr+m]*in[m];
          out[k] = sum/m_alpha;
      }
      #endif
  }

  void HankelTransformR::initDHT(const BesselRootGridR& grid){
      const std::vector<double>& J0 = grid.getBesselRoots();
      std::vector<double> J1(m_nr);

      for(auto i = 0; i < m_nr; i++)
          J1[i] = boost::math::cyl_bessel_j<double>(1.0,J0[i]);

      double jN = grid.getjN();
      for(auto m = 0; m < m_nr; m++){
          for(auto k = 0; k < m_nr; k++){
              double beta_mk = 2.0/(jN*pow(J1[k],2));
              double arg = J0[m]*J0[k]/jN;
              double J0_mk = boost::math::cyl_bessel_j<double>(0.0,arg);
              m_Ymk[m*m_nr+k] = beta_mk*J0_mk;
              m_YmkC[m*m_nr+k] = beta_mk*J0_mk;
          }
      }
  }

  HankelTransformRb::HankelTransformRb(const BesselRootGridR& grid,unsigned threads) :
      m_threads(threads),
      m_nr(grid.getNr()),
      m_Ymk(grid.getNr()*grid.getNr())
  {
      m_alpha = grid.getjN()/pow(grid.getMaxSR(),2);
      initDHT(grid);
  }

  void HankelTransformRb::R_To_SR(const double* in,double* out)
  {
      for(unsigned m = 0; m < m_nr; m++){
          double sum = 0.0;
          for(unsigned k = 0; k < m_nr; k++)
              sum += m_Ymk[m*m_nr+k]*in[k];
          out[m] = m_alpha*sum;
      }
  }

  void HankelTransformRb::R_To_SR(const dcmplx* in,dcmplx* out)
  {
      std::vector<std::thread> workers;
      for(unsigned tid = 0; tid < m_threads; tid++){
          workers.push_back(std::thread([](\
                          unsigned tid,\
                          unsigned nthreads,\
                          unsigned nr,\
                          std::vector<double>& Ymk,\
                          double alpha,\
                          const dcmplx* v,\
                          dcmplx* w){
              for(unsigned m = tid; m < nr; m+=nthreads){
                  dcmplx sum = 0.0;
                  for(unsigned k = 0; k < nr; k++)
                      sum += Ymk[m*nr+k]*v[k];
                  w[m] = alpha*sum;
              }
          },tid,m_threads,m_nr,std::ref(m_Ymk),m_alpha,in,out));
      }

      for(auto& worker : workers){
          worker.join();
      }
  }

  void HankelTransformRb::SR_To_R(const double* in,double* out)
  {

      for(unsigned k = 0; k < m_nr; k++){
          double sum = 0.0;
          for(unsigned m = 0; m < m_nr; m++)
              sum += m_Ymk[k*m_nr+m]*in[m];
          out[k] = sum/m_alpha;
      }
  }

  void HankelTransformRb::SR_To_R(const dcmplx* in,dcmplx* out)
  {
      std::vector<std::thread> workers;
      for(unsigned tid = 0; tid < m_threads; tid++){
          workers.push_back(std::thread([](\
                          unsigned tid,\
                          unsigned nthreads,\
                          unsigned nr,\
                          std::vector<double>& Ymk,\
                          double alpha,\
                          const dcmplx* v,\
                          dcmplx* w){
              for(unsigned k = tid; k < nr; k+=nthreads){
                  dcmplx sum = 0.0;
                  for(unsigned m = 0; m < nr; m++)
                      sum += Ymk[k*nr+m]*v[m];
                  w[k] = sum/alpha;
              }
          },tid,m_threads,m_nr,std::ref(m_Ymk),m_alpha,in,out));
      }

      for(auto& worker : workers){
          worker.join();
      }
  }

  void HankelTransformRb::initDHT(const BesselRootGridR& grid){
      const std::vector<double>& J0 = grid.getBesselRoots();
      std::vector<double> J1(m_nr);

      for(auto i = 0; i < m_nr; i++)
          J1[i] = boost::math::cyl_bessel_j<double>(1.0,J0[i]);

      double jN = grid.getjN();
      for(auto m = 0; m < m_nr; m++){
          for(auto k = 0; k < m_nr; k++){
              double beta_mk = 2.0/(jN*pow(J1[k],2));
              double arg = J0[m]*J0[k]/jN;
              double J0_mk = boost::math::cyl_bessel_j<double>(0.0,arg);
              m_Ymk[m*m_nr+k] = beta_mk*J0_mk;
          }
      }
  }

}


#include <iostream>
#include <math.h>

#include <boost/multiprecision/cpp_int.hpp>

using namespace std;

int target = 1000;

int main(int argc, char** argv) {
  boost::multiprecision::cpp_int n = 1;
  for (int i = 0; i < target; i++) {
    n *= 2;
  }
  unsigned long long sum = 0;
  while (n) {
    sum += static_cast<unsigned long long>(n % 10);
    n /= 10;
  }
  cout << "Sum of all digits: " << sum << endl;
  return 0;
}


/*
This is a modified version SCRIMP with optimized arithmetics.
The code builds on SCRIMP++, as published by Zhua, Yeh, Zimmerman et al. at https://sites.google.com/site/scrimpplusplus/ and contains parts from their code

Details of the SCRIMP algorithm can be found at:
(author information ommited for ICDM review),
"SCRIMP++: Motif Discovery at Interactive Speeds", submitted to ICDM 2018.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <limits>
#include <vector>
#include <algorithm>
#include <string>
#include <sstream>
#include <chrono>

#include <boost/filesystem.hpp>

#include <ScrimpSequOpt.hpp>
#include <logging.hpp>
#include <papiwrapper.hpp>

using namespace matrix_profile;

static FactoryRegistration<ScrimpSequOpt> s_sequRegistr("scrimp_sequ_opt");
static const int notification_interval_iter = 10000;

void ScrimpSequOpt::precompute_window_statistics(const int windowSize,
    const aligned_tsdtype_vec& A,
    const int ProfileLength,
    aligned_tsdtype_vec& AMeanScaledSigSqrM,
    aligned_tsdtype_vec& ASigmaInv,
    const idx_dtype ts_len)
{
	//TODO: refactore raw pointers, const method
	const size_t timeSeriesLength =(ts_len==-1)?A.size():ts_len;
	std::vector<tsa_dtype> AMean(A.size()-windowSize+1);
	AMeanScaledSigSqrM.resize(A.size()-windowSize+1);
	ASigmaInv.resize(A.size()-windowSize+1);
	const tsa_dtype sqrt_m = sqrt(static_cast<tsa_dtype>(windowSize));

	tsa_dtype* ACumSum = new tsa_dtype[timeSeriesLength];
	ACumSum[0] = A[0];
	for (int i = 1; i < timeSeriesLength; i++)
		ACumSum[i] = A[i] + ACumSum[i - 1];
	tsa_dtype* ASqCumSum = new tsa_dtype[timeSeriesLength];
	ASqCumSum[0] = A[0] * A[0];
	for (int i = 1; i < timeSeriesLength; i++)
		ASqCumSum[i] = A[i] * A[i] + ASqCumSum[i - 1];
	tsa_dtype* ASum = new tsa_dtype[ProfileLength];
	ASum[0] = ACumSum[windowSize - 1];
	for (int i = 0; i < timeSeriesLength - windowSize; i++)
		ASum[i + 1] = ACumSum[windowSize + i] - ACumSum[i];
	tsa_dtype* ASumSq = new tsa_dtype[ProfileLength];
	ASumSq[0] = ASqCumSum[windowSize - 1];
	for (int i = 0; i < timeSeriesLength - windowSize; i++)
		ASumSq[i + 1] = ASqCumSum[windowSize + i] - ASqCumSum[i];
	for (int i = 0; i < ProfileLength; i++){
		    AMean[i] = ASum[i] / windowSize;
	    }
	tsa_dtype* ASigmaSq = new tsa_dtype[ProfileLength];
	for (int i = 0; i < ProfileLength; i++)
		ASigmaSq[i] = ASumSq[i] / windowSize - AMean[i] * AMean[i];

	for (int i = 0; i < ProfileLength; i++) {
		ASigmaInv[i] = 1.0/sqrt(ASigmaSq[i]);
		AMeanScaledSigSqrM[i] = AMean[i]*ASigmaInv[i]*sqrt_m;
	}
	delete [] ACumSum;
	delete [] ASqCumSum;
	delete [] ASum;
	delete [] ASumSq;
	delete [] ASigmaSq;
}

void ScrimpSequOpt::init_diagonals(const int first_diag, const int last_diag, aligned_tsdtype_vec& initial_zs, const aligned_tsdtype_vec& A, const int windowSize)
{
	const size_t profileLength = A.size() - windowSize+1;
//	initial_zs.reserve(profileLength);
	assert(initial_zs.size() >= profileLength);
	//evaluate the fist distance value in the current diagonal
	for (size_t diag = first_diag; diag <= last_diag; ++diag) {
		tsa_dtype lastz=0;
		for (int k = 0; k < windowSize; k++)
		{
			lastz += A[k+diag]*A[k];
		}
		initial_zs[diag] = lastz;
//std::cout << "inited " << diag << " with " << lastz << std::endl;
	}
}

void ScrimpSequOpt::init_all_diagonals(aligned_tsdtype_vec& initial_zs, const aligned_tsdtype_vec& A, const int windowSize) {
	//init diagonals 0 to profileLength-1
	init_diagonals(0, A.size()-windowSize+1, initial_zs, A, windowSize);
}

void ScrimpSequOpt::eval_diagonal(aligned_int_vec& profileIndex, const aligned_tsdtype_vec& A, const aligned_tsdtype_vec& initial_zs, const int windowSize, const aligned_tsdtype_vec& ASigmaInv, const aligned_tsdtype_vec& AMeanScaledSigSqrM, const int diag, aligned_tsdtype_vec& profile)
{
	const int profileLength = AMeanScaledSigSqrM.size();
	tsa_dtype corrScore;
#ifdef PROFILING
	long updateCtr = 0;
#endif

	tsa_dtype tmpz = initial_zs[diag]; //rather use a local, to avoid innecessary writes to the referenced memory
	for (int j=diag; j<profileLength; j++)
	{
		int i=j-diag;

		corrScore = (tmpz* (ASigmaInv[j] * ASigmaInv[i]) - AMeanScaledSigSqrM[j] * AMeanScaledSigSqrM[i]) ;
		tmpz += A[j+windowSize]*A[i+windowSize]  - A[j]*A[i];

		if (corrScore > profile[j])
		{
			profile[j] = corrScore;
			profileIndex [j] = i;
#ifdef PROFILING
			updateCtr+=1;
#endif
		}
		if (corrScore > profile[i])
		{
			profile[i] = corrScore;
			profileIndex [i] = j;
#ifdef PROFILING
			updateCtr+=1;
#endif
		}
	}

#ifdef PROFILING
	_profileUpdateCounter += updateCtr;
#endif
}

void ScrimpSequOpt::compute_matrix_profile(const Scrimppp_params& params)
{
	std::chrono::high_resolution_clock::time_point tstart, tend;
	std::chrono::duration<double> time_elapsed;
	aligned_tsdtype_vec A = fetch_time_series<aligned_tsdtype_vec::allocator_type>(params); //load the time series data
	aligned_tsdtype_vec AMeanScaledSqrtM(A.size());
	aligned_tsdtype_vec ASigmaInv(A.size());
	int windowSize = params.query_window_len;
	int exclusionZone = windowSize / 4;
	int timeSeriesLength = A.size();
	int ProfileLength = timeSeriesLength - windowSize + 1;
	//Initialize Matrix Profile and Matrix Profile Index
	aligned_tsdtype_vec profile(ProfileLength, 0.0);
	aligned_int_vec profileIndex(ProfileLength, 0);
	aligned_tsdtype_vec initial_zs(timeSeriesLength); // stores products between two Timeseries values with a distinct offset
	std::vector<int> idx; // store indices of the diagonals, defining their evaluation order
	idx.reserve(ProfileLength-exclusionZone-1);

	//several monitors for performance measurement
	PerfCounters setup_perf("setup");
	PerfCounters init_diag_perf("diagonal initialization");
	PerfCounters eval_diag_perf("diagonal evaluation");

	//validation of parameters
	if (timeSeriesLength < windowSize) {
		throw std::invalid_argument("ERROR: Time series is shorter than the window length, can not proceed");
	}

	EXEC_INFO( "Sequential SCRIMP matrix profile computation with profile length " << ProfileLength << " and window size " << windowSize);

	{
		ScopedPerfAccumulator monitor(setup_perf);
		//precompute the mean and standard deviations of the sliding windows along the time series
		precompute_window_statistics(windowSize, A, ProfileLength, AMeanScaledSqrtM, ASigmaInv);

		//start time measurment
		tstart = std::chrono::high_resolution_clock::now();

		/******************** SCRIMP ********************/
		//Random shuffle the computation order of the diagonals of the distance matrix
		for (int i = exclusionZone+1; i < ProfileLength; i++) {
			idx.push_back(i);
		}
		std::random_shuffle(idx.begin(), idx.end());
	}

	// compute the first correlation values in the diagonals (i.e. compute the correlation between the first windows)
	{
		ScopedPerfAccumulator monitor(init_diag_perf);
		init_all_diagonals(initial_zs, A, windowSize);
	}
	//iteratively evaluate the diagonals of the distance matrix
	for (int ri = 0; ri < idx.size(); ri++)
	    {
		//select a random diagonal
		int diag = idx[ri];

		//evaluate the second to the last distance values along the diagonal in the matrix and update the matrix profile/matrix profile index.
		{
			ScopedPerfAccumulator monitor(eval_diag_perf);
			eval_diagonal(profileIndex, A, initial_zs, windowSize, ASigmaInv, AMeanScaledSqrtM, diag, profile);
		}

		//Show time per 10000 iterations
		if ((ri+1) % notification_interval_iter == 0)
		{
			tend = std::chrono::high_resolution_clock::now();
			time_elapsed = tend - tstart;
			EXEC_INFO ( "finished " << ri+1 << " iterations after " << std::setprecision(std::numeric_limits<tsa_dtype>::digits10 + 2) << time_elapsed.count() << " seconds.");
		}
	}

	// apply a correction of the distance values, as we dropped a factor of 2 to avoid unnecessary computations
	tsa_dtype twice_m = 2.0*static_cast<tsa_dtype>(windowSize);
	for (auto iter=profile.begin(); iter<profile.end(); ++iter) {
		(*iter) = twice_m - 2.0 * (*iter);
	}

	// end timer
	// tend = time(0);
	tend = std::chrono::high_resolution_clock::now();
	time_elapsed = tend - tstart;

	PERF_LOG ( "total computation time: " << std::setprecision(std::numeric_limits<tsa_dtype>::digits10 + 2) << time_elapsed.count() << " seconds." );
	const double triang_len = ProfileLength-exclusionZone;
	PERF_LOG ( "throughput computations: " << triang_len * triang_len / time_elapsed.count() << " matrix entries/second");

	//store the result
	store_matrix_profile(profile, profileIndex, params);

	setup_perf.log_perf();
	init_diag_perf.log_perf();
	eval_diag_perf.log_perf();
#ifdef PROFILING
	PERF_LOG ( "number of matrix profile updates: " << _profileUpdateCounter);
#endif

}


/**
 * @file   svd_armadillo.cc
 * @author Jiangwen Su <uukuguy@gmail.com>
 * @date   2014-12-12 01:06:49
 *
 * @brief
 *
 *
 */

#include "docset.h"
#include "document.h"
#include "term.h"
#include "logger.h"
#include "lexicon.h"
#include "dmat.h"
#include "smat.h"
#include "svd.h"

//#include <armadillo>
#include <mlpack/methods/quic_svd/quic_svd.hpp>
#include <mlpack/methods/regularized_svd/regularized_svd.hpp>

typedef struct svd_data_t{
    Document *pDocument;
    arma::mat &A;
} svd_data_t;

//void lexicon_term_loop_armadillo(term_t *term, void *user_data)
//{
    //svd_data_t *svd_data = (svd_data_t*)user_data;
    //doc_t *doc = svd_data->doc;
    //arma::mat &A = svd_data->A;

    //if ( doc_has_term(doc, term) == 1 ) {
        //uint32_t row = term_get_id(term);
        //uint32_t col = doc_get_id(doc);
        ////uint32_t row = doc_get_id(doc);
        ////uint32_t col = term_get_id(term);

        //double term_tfidf = doc_get_term_tfidf(doc, term);
        //A(row, col) = term_tfidf;
    //}
//}

dmat_t *docset_save_corrmat_armadillo(docset_t *docset, arma::mat& C, const char *filename)
{
    uint32_t numRows = C.n_rows;
    uint32_t numCols = C.n_cols;
    //dmat_free(docset->corrmat);

    dmat_t *corrmat = dmat_new(numRows, numCols);
    double *values = dmat_get_values(corrmat);
    for ( uint32_t row = 0 ; row < numRows ; row++ ){
        for ( uint32_t col = 0 ; col < numCols ; col++ ){
            double v = C(row, col);
            *values = v;
            values++;
        }
    }

    //docset->corrmat = corrmat;

    //dmat_save_to_csv(matrix, filename);

    return corrmat;
}

#include <fstream>
#include <iomanip>
int save_singular_value_armadillo(arma::vec &s, const char *filename)
{
    std::fstream out(filename, std::ios::out);
    out << std::setprecision(6);
    for ( uint32_t i = 0 ; i < s.size() ; i++ ){
        out << s(i) << std::endl;
    }
    return 0;
}

double vector_angle_cosine_armadillo(arma::vec &v1, arma::vec &v2, uint32_t size)
{
	// A(dot)B = |A||B|Cos(theta)
	// so Cos(theta) = A(dot)B / |A||B|

	double a_dot_b=0;
	for ( uint32_t i = 0 ; i < size ; i++ ) {
		a_dot_b += v1(i) * v2(i);
	}

	double A=0;
	for ( uint32_t j = 0 ; j < size ; j++ ) {
        A += v1(j) * v1(j);
	}
	A = sqrt(A);

	double B=0;
	for ( uint32_t k = 0 ; k < size ; k++ ) {
        B += v2(k) * v2(k);
	}
	B = sqrt(B);

	return a_dot_b / (A * B);
}

int docset_query_armadillo(docset_t *docset, const char *query_string, uint32_t dimensions)
{
    if ( query_string == NULL || strlen(query_string) == 0 )
        return -1;

    Docset *pDocset = (Docset*)(docset->pDocset);
    const Lexicon &lexicon = pDocset->get_lexicon();

    uint32_t numTerms = pDocset->get_total_terms();
    uint32_t numDocs = pDocset->get_total_docs();
    arma::vec q_vct(numTerms, arma::fill::zeros);

    std::istringstream query_stream(query_string);
    std::string word;
	while (query_stream >> word) {
        Term *pTerm = lexicon.get_term_by_text(word);
        if ( pTerm != NULL ){
            uint32_t term_id = pTerm->m_id;
            q_vct(term_id) = q_vct(term_id) + 1;
        }
    };

    //arma::vec &s = *(arma::vec*)docset->svd_s;
    //arma::mat &U = *(arma::mat*)docset->svd_U;
    //arma::mat &V = *(arma::mat*)docset->svd_V;
    arma::vec s;
    arma::mat U;
    arma::mat V;

    arma::vec d_vct(dimensions, arma::fill::zeros);
	// Dq = Xq' T S^-1
	for (uint32_t i = 0; i < dimensions; i++) {
		double sum = 0;
		for (uint32_t j = 0; j < numTerms; j++) {
            sum += q_vct(j) * U(j,i);
		}
        d_vct(i) = sum * ( 1 / s(i));
	}

	//compare each document with Dq
	for ( uint32_t n = 0 ; n < numDocs ; n++ ) {
        arma::vec t_vct(dimensions, arma::fill::zeros);
		// fill temp document vector
		for ( uint32_t m = 0 ; m < dimensions ; m++) {
            t_vct(m) = V(n,m) * s(m);
		}
        AddCorrelation(n, vector_angle_cosine_armadillo(d_vct, t_vct, dimensions));
	}

    uint32_t n = 0;
    std::vector<struct doc_cor>::const_iterator it;
	for ( it = g_cor.begin() ; it < g_cor.end() ; it++, n++ ) {
        doc_cor cor = *it;
        uint32_t doc_id = cor.doc_id;
        double correlation = cor.correlation;

        Document *pDocument = pDocset->get_document_by_id(doc_id);
        const char *doc_name = "<not found>";
        if ( pDocument != NULL ){
            doc_name = pDocument->m_title.c_str();
        }

        if ( n > g_cor.size() - 10 ) {
            warning_log("%d:<%d,%.3f>%s", n, doc_id, correlation, doc_name);
        } else if ( n < 10 ) {
            notice_log("%d:<%d,%.3f>%s", n, doc_id, correlation, doc_name);
        }

    }
    return 0;
}

int export_vector_to_csv(arma::vec V, const char *filename)
{
    std::fstream out(filename, std::ios::out);

    for ( uint32_t n = 0 ; n < V.size() ; n++ ){
        out << "C" << n;
        if ( n < V.size() - 1 )
            out << ",";
    }
    out << std::endl;

    for ( uint32_t n = 0 ; n < V.size() ; n++ ){
        out << V(n);
        if ( n < V.size() - 1 )
            out << ",";
    }
    out << std::endl;

    return 0;
}

int export_matrix_to_csv(arma::mat& C, const char *filename)
{
    uint32_t numRows = C.n_rows;
    uint32_t numCols = C.n_cols;

    std::fstream out(filename, std::ios::out);

    out << "id,";
    for ( uint32_t col = 0 ; col < numCols ; col++ ){
        out << "C" << col;
        if ( col < numCols - 1 )
            out << ",";
    }
    out << std::endl;

    for ( uint32_t row = 0 ; row < numRows ; row++ ){
        out << "R" << row << ", ";
        for ( uint32_t col = 0 ; col < numCols ; col++ ){
            double v = C(row, col);
            out << v;
            if ( col < numCols - 1 )
                out << ",";
        }
        out << std::endl;
    }
    out << std::endl;

    return 0;
}

/* ==================== docset_do_svd_armadillo() ==================== */
void docset_do_svd_armadillo(docset_t *docset, uint32_t dimensions)
{
    GET_TIME_MILLIS(msec0);

    Docset *pDocset = (Docset*)(docset->pDocset);
    uint32_t numRows = pDocset->get_total_terms();
    uint32_t numCols = pDocset->get_total_docs();
    //uint32_t totalNonZeroValues = pDocset->calculate_nonzerovalues();

    //smat_t *tfm = smat_new(numRows, numCols, totalNonZeroValues);
    smat_t *tfm = pDocset->calculate_tfmatrix();

    arma::mat A(numRows, numCols, arma::fill::zeros);
    //arma::sp_mat A(numRows, numCols);

    SMAT_LOOP_BEGIN(tfm, numRows, numCols);
    A(row, col) = value;
    SMAT_LOOP_END();
    //uint32_t v = 0;
    //for ( uint32_t col = 0 ; col < numCols ; col++ ){
        //for ( ; v < tfm->pointr[col + 1]; v++) {
            //uint32_t row = tfm->rowind[v];
            //double value = tfm->values[v];
            //A(row, col) = value;
        //}

    //}

    smat_free(tfm);

    GET_TIME_MILLIS(msec1);
    notice_log("svd prepare: %llu.%03llu sec.", (msec1 - msec0) / 1000, (msec1 - msec0) % 1000);

    //docset->svd_U = (void*)new arma::mat();
    //docset->svd_s = (void*)new arma::vec();
    //docset->svd_V = (void*)new arma::mat();
    //arma::mat &U = *(arma::mat*)docset->svd_U;
    //arma::vec &s = *(arma::vec*)docset->svd_s;
    //arma::mat &V = *(arma::mat*)docset->svd_V;
    arma::mat U;
    arma::vec s;
    arma::mat V;

    //uint32_t rank = 10;
    //uint32_t iterations = 10;
    //double alpha = 0.01;
    //double lambda = 0.02;
    //mlpack::svd::RegularizedSVD<> svd(A, U, V, rank, iterations, alpha, lambda);

    mlpack::svd::QUIC_SVD svd(A, U, V, s, 0.03, 0.1);

    //const char *side = "both";
    ////const char *side = "left";
    ////const char *side = "right";
    ////const char *mode = "d";
    //const char *mode = "s";
    //arma::svd_econ(U, s, V, A, side, mode);

    uint32_t sv_cnt = s.size();
    printf("Singular Values (%d,%d)\n", sv_cnt, sv_cnt);
    for ( uint32_t n = 0 ; n < sv_cnt ; n++ ){
        if ( n < 20 ) {
            printf("%.6f ", s(n));
        } else if ( n == 20 ){
            printf("\n......\n");
        } else if ( n > sv_cnt - 20) {
            printf("%.6f ", s(n));
        }
    }
    printf("\nU(%d,%d) s(%d,%d) V(%d, %d)\n", U.n_rows, U.n_cols, s.n_rows, s.n_cols, V.n_rows, V.n_cols);

    GET_TIME_MILLIS(msec2);

    save_singular_value_armadillo(s, "test-s");

    // Reduce dimensions
    arma::mat S(s.n_rows, s.n_rows, arma::fill::zeros);
    for ( uint32_t i = 0 ; i < s.n_rows ; i++ ){
        if ( i >= dimensions )
            s(i) = 0.0;
        S(i,i) = s(i);
    }

    //printf("Building CorrMatrix...\n");
    //arma::mat C = U * S * V;

    GET_TIME_MILLIS(msec21);

    printf("Saving CorrMatrix...\n");
    //docset_save_corrmat_armadillo(docset, C, "test.corrmat");

    export_matrix_to_csv(U, "./U.csv");
    export_matrix_to_csv(V, "./V.csv");
    export_vector_to_csv(s, "./s.csv");

    GET_TIME_MILLIS(msec3);

    notice_log("svd do: %llu.%03llu sec.", (msec2 - msec1) / 1000, (msec2 - msec1) % 1000);
    notice_log("Build CorrMatrix do: %llu.%03llu sec.", (msec21 - msec2) / 1000, (msec21 - msec2) % 1000);
    notice_log("Save CorrMatrix do: %llu.%03llu sec.", (msec3 - msec21) / 1000, (msec3 - msec21) % 1000);

    notice_log("svd total: %llu.%03llu sec.", (msec3 - msec0) / 1000, (msec3 - msec0) % 1000);

}


//  (C) Copyright Nick Thompson 2019.
//  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)

#ifndef BOOST_MATH_TOOLS_CONDITION_NUMBERS_HPP
#define BOOST_MATH_TOOLS_CONDITION_NUMBERS_HPP
#include <cmath>
#include <boost/math/differentiation/finite_difference.hpp>

namespace boost::math::tools {

template<class Real, bool kahan=true>
class summation_condition_number {
public:
    summation_condition_number(Real const x = 0)
    {
        using std::abs;
        m_l1 = abs(x);
        m_sum = x;
        m_c = 0;
    }

    void operator+=(Real const & x)
    {
        using std::abs;
        // No need to Kahan the l1 calc; it's well conditioned:
        m_l1 += abs(x);
        if constexpr(kahan)
        {
            Real y = x - m_c;
            Real t = m_sum + y;
            m_c = (t-m_sum) -y;
            m_sum = t;
        }
        else
        {
            m_sum += x;
        }
    }

    inline void operator-=(Real const & x)
    {
        this->operator+=(-x);
    }

    // Is operator*= relevant? Presumably everything gets rescaled,
    // (m_sum -> k*m_sum, m_l1->k*m_l1, m_c->k*m_c),
    // but is this sensible? More important is it useful?
    // In addition, it might change the condition number.

    [[nodiscard]] Real operator()() const
    {
        using std::abs;
        if (m_sum == Real(0) && m_l1 != Real(0))
        {
            return std::numeric_limits<Real>::infinity();
        }
        return m_l1/abs(m_sum);
    }

    [[nodiscard]] Real sum() const
    {
        // Higham, 1993, "The Accuracy of Floating Point Summation":
        // "In [17] and [18], Kahan describes a variation of compensated summation in which the final sum is also corrected
        // thus s=s+e is appended to the algorithm above)."
        return m_sum + m_c;
    }

    [[nodiscard]] Real l1_norm() const
    {
        return m_l1;
    }

private:
    Real m_l1;
    Real m_sum;
    Real m_c;
};

template<class F, class Real>
Real evaluation_condition_number(F const & f, Real const & x)
{
    using std::abs;
    using std::isnan;
    using std::sqrt;
    using boost::math::differentiation::finite_difference_derivative;

    Real fx = f(x);
    if (isnan(fx))
    {
        return std::numeric_limits<Real>::quiet_NaN();
    }
    bool caught_exception = false;
    Real fp;
    try
    {
        fp = finite_difference_derivative(f, x);
    }
    catch(...)
    {
        caught_exception = true;
    }

    if (isnan(fp) || caught_exception)
    {
        // Check if the right derivative exists:
        fp = finite_difference_derivative<decltype(f), Real, 1>(f, x);
        if (isnan(fp))
        {
            // Check if a left derivative exists:
            const Real eps = (std::numeric_limits<Real>::epsilon)();
            Real h = - 2 * sqrt(eps);
            h = boost::math::differentiation::detail::make_xph_representable(x, h);
            Real yh = f(x + h);
            Real y0 = f(x);
            Real diff = yh - y0;
            fp = diff / h;
            if (isnan(fp))
            {
                return std::numeric_limits<Real>::quiet_NaN();
            }
        }
    }

    if (fx == 0)
    {
        if (x==0 || fp==0)
        {
            return std::numeric_limits<Real>::quiet_NaN();
        }
        return std::numeric_limits<Real>::infinity();
    }

    return abs(x*fp/fx);
}

}
#endif


/*
 * $Revision: 615 $ $Date: 2011-06-22 12:02:16 -0700 (Wed, 22 Jun 2011) $
 *
 * Copyright by Astos Solutions GmbH, Germany
 *
 * this file is published under the Astos Solutions Free Public License
 * For details on copyright and terms of use see
 * http://www.astos.de/Astos_Solutions_Free_Public_License.html
 */

#include "Atmosphere.h"
#include "TextureMap.h"
#include "Units.h"
#include "Debug.h"
#include "Intersect.h"
#include "DataChunk.h"
#include "internal/InputDataStream.h"
#include "internal/OutputDataStream.h"
#include <GL/glew.h>
#include <Eigen/Array>
#include <cmath>

#include <iostream>
#include <fstream>

using namespace vesta;
using namespace Eigen;
using namespace std;

// Indices of refraction are from http://physics.info/refraction/ )

/** Index of refraction of air at 0 degrees C.
  */
const double Atmosphere::IndexOfRefraction_Air_0 = 1.00029238;

/** Index of refraction of air at 15 degrees C.
  */
const double Atmosphere::IndexOfRefraction_Air_15 = 1.00027712;

// Density of air in kilograms per cubic meter at:
//   0 degrees C
//   15 degrees C
static const double Density_Air_0 = 1.292;
static const double Density_Air_15 = 1.225;

// Mass of one mole of air in kilograms
static const double MolarMass_Air = 0.0289644;

static const double Mole = 6.0221415e23;

static const float MieScattering_ClearSky = 2.10e-6f;

/** Molecules of air per cubic meter at sea level on Earth at 0 degrees C
  */
const double Atmosphere::MolecularDensity_Air_0 = Mole * Density_Air_0 / MolarMass_Air;

/** Molecules of air per cubic meter at sea level on Earth at 15 degrees C
  */
const double Atmosphere::MolecularDensity_Air_15 = Mole * Density_Air_15 / MolarMass_Air;

static const double EarthEquatorialRadius = 6378.14;

static const Vector3d standardWavelengths(650.0, 550.0, 440.0);

// Calculate the Rayleigh scattering coefficient for the specified
// wavelength (in nanometers), index of refraction n, and molecular
// density (particles per cubic meter.)
static double rayleighScattering(double wavelength, double n, double N)
{
    return (8.0 * pow(PI, 3.0) * pow(n * n - 1.0, 2.0)) / (3.0 * N * pow(wavelength * 1.0e-9, 4.0));
}

// Temporary workaround for an apparent Eigen bug with g++ 4.2 on Mac OS X. We need to avoid
// using the vector::resize() method on Eigen's vector specialization for objects that require
// alignment.
template<typename V> void resizeVector(V& v, typename V::size_type new_size, const typename V::value_type& x)
{
    if (new_size < v.size())
    {
        v.erase(v.begin() + new_size, v.end());
    }
    else if (new_size > v.size())
    {
        v.insert(v.end(), new_size - v.size(), x);
    }
}

/** Construct a new atmosphere with default values approximately correct
  * for Earth.
  */
Atmosphere::Atmosphere() :
    m_planetRadius(float(EarthEquatorialRadius)),
    m_rayleighScaleHeight(8.0f),
    m_mieScaleHeight(1.2f),
    m_mieScatteringCoeff(MieScattering_ClearSky),
    m_mieAsymmetry(0.76f),
    m_absorptionCoeff(Vector3f::Zero()),
    m_transmittanceHeightSamples(0),
    m_transmittanceViewAngleSamples(0),
    m_scatterHeightSamples(0),
    m_scatterViewAngleSamples(0),
    m_scatterSunAngleSamples(0)
{
    computeRayleighScatteringCoeff(IndexOfRefraction_Air_15, MolecularDensity_Air_15);
}

Atmosphere::~Atmosphere()
{
}

/** Compute realistic Rayleigh scattering coefficients for the specified index
  * of refraction n and molecular density N.
  *
  * @param n index of refraction
  * @param N molecular density at ground level in molecules / cubic meter.
  */
void
Atmosphere::computeRayleighScatteringCoeff(double n, double N)
{
    Vector3d coeff(rayleighScattering(standardWavelengths.x(), n, N),
                   rayleighScattering(standardWavelengths.y(), n, N),
                   rayleighScattering(standardWavelengths.z(), n, N));
    m_rayleighScatteringCoeff = coeff.cast<float>();
}

/** Get the approximate color of the atmosphere due to Rayleigh scattering
  * over the specified distance in meters. This is used for simplified
  * atmosphere rendering that doesn't include all the effects of scattering.
  */
Spectrum
Atmosphere::color(float distance) const
{
    Vector3f s = distance * m_rayleighScatteringCoeff;
    Vector3f rgb = Vector3f::Ones() - Vector3f(exp(-s.x()), exp(-s.y()), exp(-s.z()));

    // Normalize the color
    rgb /= rgb.maxCoeff();

    return Spectrum(rgb.x(), rgb.y(), rgb.z());
}

/** Get the height at which the atmosphere is effectively transparent.
  * The density of the atmosphere decreases exponentially with altitude. Although
  * it is never zero, in practice we need to choose some finite volume for rendering
  * the atmospheric halo around a planet. We choose a height large enough to avoid
  * a sharp cutoff artifact, but small enough so that the GPU doesn't waste cycles
  * drawing a lot of transparent pixels.
  */
float
Atmosphere::transparentHeight() const
{
    return 8.0f * max(m_rayleighScaleHeight, m_mieScaleHeight);
}

TextureMap*
Atmosphere::transmittanceTexture() const
{
    return m_transmittanceTexture.ptr();
}

TextureMap*
Atmosphere::scatterTexture() const
{
    return m_scatterTexture.ptr();
}

/** Build precomputed scattering tables. generateTextures() must be called after this function in
  * order to be able to render objects with precomputed atmospheric scattering.
  */
void
Atmosphere::computeScattering(unsigned int heightSamples, unsigned int viewAngleSamples, unsigned int sunAngleSamples)
{
    computeTransmittanceTable(DefaultTransmittanceTableHeightSamples,
                              DefaultTransmittanceTableViewAngleSamples);
    computeInscatterTable(heightSamples,
                          viewAngleSamples,
                          sunAngleSamples);
}

/** Build precomputed scattering tables with the default dimensions. generateTextures()
  * must be called after this function in order to be able to render objects with precomputed
  * atmospheric scattering.
  */
void
Atmosphere::computeScattering()
{
    computeScattering(DefaultScatterTableHeightSamples,
                      DefaultScatterTableViewAngleSamples,
                      DefaultScatterTableSunAngleSamples);
}

void
Atmosphere::generateTextures()
{
    generateTransmittanceTexture();
    generateInscatterTexture();
}

void
Atmosphere::generateTransmittanceTexture()
{
    unsigned int tableSize = m_transmittanceHeightSamples * m_transmittanceViewAngleSamples;
    if (tableSize < 1)
    {
        VESTA_LOG("Zero size transmittance table for atmosphere");
        return;
    }

    assert(m_transmittanceTable.size() >= tableSize);

    for (unsigned int i = 0; i < m_transmittanceHeightSamples * m_transmittanceViewAngleSamples; ++i)
    {
        m_transmittanceTable[i] = Vector3f(max(0.00001f, min(256.0f, m_transmittanceTable[i].x())),
                                           max(0.00001f, min(256.0f, m_transmittanceTable[i].y())),
                                           max(0.00001f, min(256.0f, m_transmittanceTable[i].z())));
    }

    GLuint texId = 0;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);

    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGB16F,
                 m_transmittanceViewAngleSamples, m_transmittanceHeightSamples,
                 0,
                 GL_RGB, GL_FLOAT,
                 &m_transmittanceTable[0]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Do not enable mipmapping, as it causes artifacts in some atmospheres (e.g. Titan)
    // at the outer edge. This could probably be resolved with a custom mipmap generation
    // algorithm, but for now, we'll just leave mipmaps off.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    m_transmittanceTexture = new TextureMap(texId, TextureProperties(TextureProperties::Clamp));

    if (GLEW_EXT_framebuffer_object)
    {
        glGenerateMipmapEXT(GL_TEXTURE_2D);
    }
    else
    {
        // Can't create mipmaps, so reset filtering to linear; it's unlikely that
        // we'll take this path since any GPU that supports floating point textures
        // and GLSL will also have FBOs.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

void
Atmosphere::generateInscatterTexture()
{
    GLuint scatterTexId = 0;
    glGenTextures(1, &scatterTexId);
    glBindTexture(GL_TEXTURE_3D, scatterTexId);

    // Clamp scatter table values before converting them to half-floats. On at least one driver,
    // the conversion from 32-bit float to 16-bit half float seems to be performed incorrectly for
    // values very near zero.
    unsigned int tableSize = m_scatterSunAngleSamples * m_scatterViewAngleSamples * m_scatterHeightSamples;
    for (unsigned int i = 0; i < tableSize; ++i)
    {
        m_inscatterTable[i] = Vector4f(max(0.00001f, min(256.0f, m_inscatterTable[i].x())),
                                       max(0.00001f, min(256.0f, m_inscatterTable[i].y())),
                                       max(0.00001f, min(256.0f, m_inscatterTable[i].z())),
                                       max(0.00001f, min(256.0f, m_inscatterTable[i].w())));
    }

    glTexImage3D(GL_TEXTURE_3D,
                 0,
                 GL_RGBA16F,
                 m_scatterSunAngleSamples, m_scatterViewAngleSamples, m_scatterHeightSamples,
                 0,
                 GL_RGBA, GL_FLOAT,
                 &m_inscatterTable[0]);

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    m_scatterTexture = new TextureMap(scatterTexId, TextureProperties(TextureProperties::Clamp));

    glBindTexture(GL_TEXTURE_3D, 0);
}

static float sign(float x)
{
    if (x > 0.0f)
        return 1.0f;
    else if (x < 0.0f)
        return -1.0f;
    else
        return 0.0f;
}

// h is the viewer's height above the planet surface
// atmRadius must be larger than planetRadius
static float opticalPathLength(float planetRadius, float atmRadius, float h, float cosViewAngle)
{
    // Gamma is 180 - view angle
    float cosGamma = -cosViewAngle;
    float sinGamma2 = 1.0f - cosGamma * cosGamma;

    float r = planetRadius + h;
    float c = r * r * sinGamma2;

    float disc = planetRadius * planetRadius - c;
    if (disc > 0.0f && cosGamma > 0.0f)
    {
        return r * cosGamma - sqrt(disc);
    }
    else
    {
        disc = atmRadius * atmRadius - c;
        return r * cosGamma + sqrt(disc);
    }
}

// Analytic calculation of optical depth
// Based on approximation from E. Bruneton and F. Neyret, "Precomputed Atmospheric Scattering" (2008)
//     - r is distance of the eye from planet center
//     - cosZenithAngle is the cosine of the angle between the zenith and view direction
//     - pathLength is the distance that the ray travels through the atmosphere
//     - H is the scale height
static float opticalDepth(float r, float cosZenithAngle, float pathLength, float H, float planetRadius)
{
    // C++ version of this GLSL function:
    // float opticalDepth(float r, float zAngle, float pathLength, float H)" << endl;
    // {
    //     float a = sqrt(r * (0.5 / H));
    //     vec2 b = a * vec2(zAngle, zAngle + pathLength / r);
    //     vec2 b2 = b * b;
    //     vec2 signB = sign(b);
    //     float x = signB.y > signB.x ? exp(b2.x) : 0.0;
    //     vec2 y = signB / (2.3193 * abs(b) + sqrt(1.52 * b2 + 4.0)) * vec2(1.0, exp(-pathLength / H * (pathLength / (2.0 * r) + zAngle)));
    //     return sqrt((6.283185 * H) * r) * exp((planetRadius - r) / H) * (x + dot(y, vec2(1.0, -1.0)));
    // }

    float a = sqrt(r * (0.5f / H));

    Vector2f b = a * Vector2f(cosZenithAngle, cosZenithAngle + pathLength / r);
    Vector2f b2 = b.cwise().square();
    Vector2f signB(sign(b.x()), sign(b.y()));

    float x = signB.y() > signB.x() ? exp(b2.x()) : 0.0f;

    float k = exp(-pathLength / H * (pathLength / (2.0f * r) + cosZenithAngle));
    float yx = signB.x() / (2.3193f * abs(b.x()) + sqrt(1.52f * b2.x() + 4.0f));
    float yy = signB.y() / (2.3193f * abs(b.y()) + sqrt(1.52f * b2.y() + 4.0f)) * k;
    return sqrt((6.283185f * H) * r) * exp((planetRadius - r) / H) * (x + yx - yy);
}

Vector3f
Atmosphere::transmittance(float r, float cosZenithAngle, float pathLength) const
{
    float odMie      = opticalDepth(r, cosZenithAngle, pathLength, m_mieScaleHeight, m_planetRadius);
    float odRayleigh = opticalDepth(r, cosZenithAngle, pathLength, m_rayleighScaleHeight, m_planetRadius);

    const Vector3f exR = m_rayleighScatteringCoeff * 1000.0f;
    const Vector3f exM = (Vector3f::Constant(m_mieScatteringCoeff) + m_absorptionCoeff) * 1000.0f;

    return (-odMie * exM - odRayleigh * exR).cwise().exp();
}

// Compute the transmittance by looking up the value in the precomputed table. Perform
// bilinear interpolation among table values.
Eigen::Vector3f
Atmosphere::transmittance(float r, float cosZenithAngle) const
{
    const unsigned int width = m_transmittanceViewAngleSamples;
    const unsigned int height = m_transmittanceHeightSamples;

    float u = cosZenithAngle * 0.5f + 0.5f;
    float v = sqrt((r - m_planetRadius) / transparentHeight());
    u = max(0.0f, min(0.99999f, u));
    v = max(0.0f, min(0.99999f, v));

    float x = u * (width - 1);
    float y = v * (height - 1);
    int ix = (int) x;
    int iy = (int) y;
    float fx = x - ix;
    float fy = y - iy;

    int index = width * iy + ix;
    Vector3f v0 = m_transmittanceTable[index] * (1.0f - fx) + m_transmittanceTable[index + 1] * fx;
    Vector3f v1 = m_transmittanceTable[index + width] * (1.0f - fx) + m_transmittanceTable[index + width + 1] * fx;

    return v0 * (1.0f - fy) + v1 * fy;
}

// Non-linear table parametrization:
//   0 <= t <= 1
//
//   height:             h(t) = t^2 * transparentHeight
//   cos(view angle):    mu(t) = toCosViewAngle()
//   cos(sun angle):     muS(t) = toCosSunAngle()
//
// Inverse mappings:
//   height:             t = sqrt(h / transparentHeight)
//   cos(view angle):    t =
//   cos(sun angle):     t =
//
// Notes:
//   - View and sun angles are both measured from the zenith
//

// Map a value in [0, 1] to the cosine of the viewing angle
// This function replaces the parametrization used in Bruneton's paper:
//     mu = -0.15f + tan(1.5f * v) / tan(1.5f) * 1.15f
//
// The change avoids an expensive arctangent function in the shader
// code.
//
// The mapping may be tuned by adjusting the value of the parameter b.
// b of 0.15 works well for Earth; a larger value should be chosen when the
// atmosphere extends higher relative to the planet radius.
static inline float toCosViewAngle(float u)
{
    float x = u * 2.0f - 1.0f;
    float sn = x < 0.0f ? 1.0f : -1.0f;
    return (x * (0.1f - 0.15f * sn) - 0.165f) / (sn * x + 1.1f);
}

// Map a value in [0, 1] to the cosine of the sun angle
static inline float toCosSunAngle(float u)
{
    // Modified from version used in Bruneton paper. This one covers a wider range
    // of sun angles, which is necessary for larger scale height / planet radius
    // ratios (e.g. Titan)
    return (log(1.0f - u * (1.0f - exp(-2.6f))) + 0.6f) / -2.0f;
}

// Fill a table with transmittance values.
//
// Transmittance in a spherical atmosphere can be described as a function of
// two parameters:
//    h - the height of the viewer above the planet surface
//    mu - the cosine of the view angle (angle between the view direction and the zenith)
void
Atmosphere::computeTransmittanceTable(unsigned int heightSamples,
                                      unsigned int viewAngleSamples)
{
    m_transmittanceHeightSamples = heightSamples;
    m_transmittanceViewAngleSamples = viewAngleSamples;
    m_transmittanceTable.resize(heightSamples * viewAngleSamples);

    float maxHeight = transparentHeight();
    float minHeight = m_planetRadius * 1.0e-6f;
    const unsigned int integrationSteps = 20;

    // Calculate the extinction coefficients. The are computed separately for Mie and Rayleigh
    // scattering particles since their densities will generally be described with different
    // scale heights.
    const Vector3f Er = m_rayleighScatteringCoeff * 1000.0f;
    const Vector3f Em = (Vector3f::Constant(m_mieScatteringCoeff) + m_absorptionCoeff) * 1000.0f;

    VESTA_LOG("Rayleigh extinction: %f %f %f", Er.x(), Er.y(), Er.z());
    VESTA_LOG("Mie extinction: %f %f %f", Em.x(), Em.y(), Em.z());

    for (unsigned int i = 0; i < heightSamples; ++i)
    {
        float v = float(i) / float(heightSamples);
        float h = minHeight + v * v * maxHeight;

        // Calculate the eye position from h
        Vector3f eye = Vector3f::UnitZ() * (m_planetRadius + h);

        for (unsigned int j = 0; j < viewAngleSamples; ++j)
        {
            float u = float(j) / float(viewAngleSamples - 1);
            float mu = toCosViewAngle(u);

            // Calculate the view direction from mu
            float cosTheta = mu;
            float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta));
            Vector3f viewDir(sinTheta, 0.0f, cosTheta);

            float pathLength = 0.0f;
            // The view ray will intersect either the planet or the atmosphere shell geometry
            if (!TestRaySphereIntersection(eye, viewDir, Vector3f::Zero(), m_planetRadius, &pathLength))
            {
                TestRaySphereIntersection(eye, viewDir, Vector3f::Zero(), m_planetRadius + maxHeight, &pathLength);
            }

            // Compute the intersection point
            Vector3f x0 = eye + pathLength * viewDir;

#if 0
            // Numerical integration to compute transmittance
            Vector3f step = (x0 - eye) / float(integrationSteps);
            float stepLength = pathLength / float(integrationSteps);

            // Sum to get the integral of optical depth between the eye and the intersection
            // point.
            Vector3f p = eye;
            float Tr = 0.0f;
            float Tm = 0.0f;

            for (unsigned int k = 0; k < integrationSteps; ++k)
            {
                float s = p.norm() - m_planetRadius;

                Tr += exp(-s / m_rayleighScaleHeight);
                Tm += exp(-s / m_mieScaleHeight);
                p += step;
            }
            Vector3f opticalDepth = (Er * Tr + Em * Tm) * stepLength;
            Vector3f xmit = (-opticalDepth).cwise().exp();
#else
            // Use analytic transmittance calculation
            Vector3f xmit = transmittance(eye.z(), viewDir.z(), pathLength);
#endif
            m_transmittanceTable[i * viewAngleSamples + j] = xmit;
        }
    }
}

// Fill a table with scattering values.
//
// Scattering in a spherical atmosphere can be described as a function of
// three parameters:
//    h - the height of the viewer above the planet surface
//    mu - the cosine of the view angle (angle between the view direction and the zenith)
//    muS - the cosine of the sun angle (angle between sun and zenith)
void
Atmosphere::computeInscatterTable(unsigned int heightSamples,
                                  unsigned int viewAngleSamples,
                                  unsigned int sunAngleSamples)
{
    m_scatterHeightSamples = heightSamples;
    m_scatterViewAngleSamples = viewAngleSamples;
    m_scatterSunAngleSamples = sunAngleSamples;

    unsigned int tableSize = m_scatterHeightSamples * m_scatterViewAngleSamples * m_scatterSunAngleSamples;
    if (tableSize < 1)
    {
        return;
    }

    //m_inscatterTable.resize(tableSize);
    resizeVector(m_inscatterTable, tableSize, Vector4f::Zero());
    if (m_inscatterTable.size() != tableSize)
    {
        return;
    }

    float maxHeight = transparentHeight();
    float minHeight = m_planetRadius * 1.0e-6f;
    const unsigned int integrationSteps = 25;

    float atmRadius = m_planetRadius + transparentHeight();

    // Calculate scattering coefficients. These are the same as the extinction coefficients
    // exception that absorption by Mie scattering particles isn't a factor.
    const Vector3f Sr = m_rayleighScatteringCoeff * 1000.0f;
    const float Sm = m_mieScatteringCoeff * 1000.0f;
    const Vector4f scatterFactors = Vector4f(Sr.x(), Sr.y(), Sr.z(), Sm);

    for (unsigned int i = 0; i < heightSamples; ++i)
    {
        VESTA_LOG("Scatter texture layer: %d", i);
        float w = float(i) / float(heightSamples);
        float h = minHeight + w * w * maxHeight;

        // Calculate the eye position from h
        Vector3f eye = Vector3f::UnitZ() * (m_planetRadius + h);

        for (unsigned int j = 0; j < viewAngleSamples; ++j)
        {
            float v = float(j) / float(viewAngleSamples - 1);
            //float mu = 2.0f * v - 1.0f;
            //float x = v * 2.0f - 1.0f;

            //float mu = (x * 0.1f) / (1.1f - abs(x));
            //float sn = x + 0.15f < 0.0f ? 1.0f : -1.0f;
            //float mu = (x * (0.1f - 0.15f * sn) - 0.165f) / (sn * x + 1.1f);
            float mu = toCosViewAngle(v);

            // Calculate the view direction from mu
            float cosTheta = mu;
            float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta));
            Vector3f viewDir(sinTheta, 0.0f, cosTheta);

            float pathLength = 0.0f;
            // The view ray will intersect either the planet or the atmosphere shell geometry
            if (!TestRaySphereIntersection(eye, viewDir, Vector3f::Zero(), m_planetRadius, &pathLength))
            {
                TestRaySphereIntersection(eye, viewDir, Vector3f::Zero(), m_planetRadius + maxHeight, &pathLength);
            }

            // Compute the intersection point
            Vector3f x0 = eye + pathLength * viewDir;

            Vector3f step = (x0 - eye) / float(integrationSteps);
            float stepLength = pathLength / float(integrationSteps);

            Vector3f viewRayTransmittance = transmittance(eye.z(), viewDir.z(), pathLength);
            //Vector3f viewRayTransmittance = transmittance(eye.z(), viewDir.z());

            for (unsigned int k = 0; k < sunAngleSamples; ++k)
            {
                float u = float(k) / float(sunAngleSamples - 1);
                //float muS = 2.0f * u - 1.0f;
                float muS = toCosSunAngle(u);//(log(1.0f - u * (1.0f - exp(-3.6f))) + 0.6f) / -3.0f;

                // Calculate the sun direction from mu
                float cosPhi = muS;
                float sinPhi2 = 1.0f - cosPhi * cosPhi;
                float sinPhi = sqrt(max(0.0f, sinPhi2));
                Vector3f sunDir(sinPhi, 0.0f, cosPhi);

                // Sum to get the integral of optical depth between the eye and the intersection
                // point.
                Vector3f p = eye;
                Vector4f inscatter = Vector4f::Zero();

                for (unsigned int l = 0; l < integrationSteps; ++l)
                {
                    float r = p.norm();
                    float s = r - m_planetRadius;

                    // Compute the transmittance along the view ray
                    Vector3f viewXmit = transmittance(eye.z(), viewDir.z(), l * stepLength);
                    //Vector3f viewXmit = viewRayTransmittance.cwise() / transmittance(r, p.dot(viewDir) / r, pathLength - (l + 1) * stepLength);
                    //Vector3f viewXmit = viewRayTransmittance.cwise() / transmittance(r, p.dot(viewDir) / r);

                    float cosPsi = p.dot(sunDir) / r;
                    float sinPsi2 = 1.0f - cosPsi * cosPsi;

                    // Compute the transmittance along the path to the sun
                    float sunPathLength = -r * cosPsi + sqrt(atmRadius * atmRadius - r * r * sinPsi2);
                    Vector3f sunXmit = transmittance(r, cosPsi, sunPathLength);
                    float d1 = opticalDepth(r, cosPsi, sunPathLength, m_rayleighScaleHeight, m_planetRadius);
                    float d2 = opticalDepth(r, cosPsi, sunPathLength, m_mieScaleHeight, m_planetRadius);

                    Vector3f xmit = sunXmit.cwise() * viewXmit;
                    inscatter.start<3>() += (exp(-s / m_rayleighScaleHeight) * stepLength) * xmit;
                    inscatter.w() += exp(-s / m_mieScaleHeight) * stepLength * xmit.x();

                    p += step;
                }

                m_inscatterTable[(i * viewAngleSamples + j) * sunAngleSamples + k] = inscatter.cwise() * scatterFactors;
            }
        }
    }
}

/** Load an atmosphere from the contents of a .atmscat file. generateTextures() must be
  * after this function in order to be able to render objects with precomputed
  * atmospheric scattering.
  *
  * atmscat file header format:
  *
  * bytes          contents
  * -------------------------------
  * 0-7            header string ("atmscatr")
  * 8-11           version identifier (uint32)
  * 12-15          Rayleigh scale height (float)
  * 16-27          Rayleigh scattering coefficients (3 floats)
  * 28-31          Mie scale height (float)
  * 32-35          Mie scattering coefficient (float)
  * 36-39          Mie asymmetry parameter (float)
  * 40-51          Absorption coefficients (3 floats)
  * 52-55          Planet radius (float)
  * 56-63          Transmittance table dimensions (2 uint32, width * height)
  * 64-75          Scattering table dimensions (3 uint32, width * height * depth)
  *
  * transmittance table (width * height * 3 floats)
  * scattering table (width * height * depth * 4 floats)
  */
Atmosphere*
Atmosphere::LoadAtmScat(const DataChunk* data)
{
    string str(data->data(), data->size());
    InputDataStream in(str);
    in.setByteOrder(InputDataStream::BigEndian);

    in.setByteOrder(InputDataStream::LittleEndian);

    char header[8];
    in.readData(header, sizeof(header));
    if (string(header, sizeof(header)) != "atmscatr")
    {
        VESTA_LOG("Incorrect header in atmscat file.");
        return NULL;
    }

    v_uint32 version = in.readInt32();
    if (in.status() != InputDataStream::Good)
    {
        VESTA_LOG("Error reading header of atmscat file.");
        return NULL;
    }

    if (version != 1)
    {
        VESTA_LOG("Unsupported atmscat file version %u", version);
        return NULL;
    }

    float HR = in.readFloat();
    Vector3f rayleighCoeff;
    rayleighCoeff.x() = in.readFloat();
    rayleighCoeff.y() = in.readFloat();
    rayleighCoeff.z() = in.readFloat();
    float HM = in.readFloat();
    float mieCoeff = in.readFloat();
    float mieAsymmetry = in.readFloat();
    Vector3f absorptionCoeff;
    absorptionCoeff.x() = in.readFloat();
    absorptionCoeff.y() = in.readFloat();
    absorptionCoeff.z() = in.readFloat();
    float planetRadius = in.readFloat();

    unsigned int transmitWidth = in.readUint32();
    unsigned int transmitHeight = in.readUint32();
    unsigned int scatterWidth = in.readUint32();
    unsigned int scatterHeight = in.readUint32();
    unsigned int scatterDepth = in.readUint32();

    if (in.status() != InputDataStream::Good)
    {
        VESTA_LOG("Error reading header of atmscat file.");
        return NULL;
    }

    if (transmitWidth == 0 || transmitHeight == 0)
    {
        VESTA_LOG("Bad atmscat file (zero dimension for transmittance table)");
        return NULL;
    }

    if (scatterWidth == 0 || scatterHeight == 0 || scatterDepth == 0)
    {
        VESTA_LOG("Bad atmscat file (zero dimension for inscatter table)");
        return NULL;
    }

    Atmosphere* atmosphere = new Atmosphere();
    atmosphere->setRayleighScaleHeight(HR);
    atmosphere->setRayleighScatteringCoeff(rayleighCoeff);
    atmosphere->setMieScaleHeight(HM);
    atmosphere->setMieScatteringCoeff(mieCoeff);
    atmosphere->setMieAsymmetry(mieAsymmetry);
    atmosphere->setAbsorptionCoeff(absorptionCoeff);
    atmosphere->setPlanetRadius(planetRadius);

    atmosphere->m_transmittanceHeightSamples = transmitHeight;
    atmosphere->m_transmittanceViewAngleSamples = transmitWidth;
    atmosphere->m_transmittanceTable.resize(transmitWidth * transmitHeight);
    if (atmosphere->m_transmittanceTable.size() != transmitWidth * transmitHeight)
    {
        VESTA_LOG("Out of memory error (allocating atmosphere transmittance table)");
        delete atmosphere;
        return NULL;
    }

    for (unsigned int i = 0; i < transmitWidth * transmitHeight; ++i)
    {
        Vector3f v;
        v.x() = in.readFloat();
        v.y() = in.readFloat();
        v.z() = in.readFloat();
        atmosphere->m_transmittanceTable[i] = v;
    }

    if (in.status() != InputDataStream::Good)
    {
        VESTA_LOG("Error reading transmittance table in atmscat file.");
        delete atmosphere;
        return NULL;
    }

    unsigned int scatterTableEntries = scatterWidth * scatterHeight * scatterDepth;
    atmosphere->m_scatterHeightSamples = scatterDepth;
    atmosphere->m_scatterViewAngleSamples = scatterHeight;
    atmosphere->m_scatterSunAngleSamples = scatterWidth;
    //atmosphere->m_inscatterTable.resize(scatterTableEntries);
    resizeVector(atmosphere->m_inscatterTable, scatterTableEntries, Vector4f::Zero());
    if (atmosphere->m_inscatterTable.size() != scatterTableEntries)
    {
        VESTA_LOG("Out of memory error (allocating atmosphere inscatter table)");
        delete atmosphere;
        return NULL;
    }

    for (unsigned int i = 0; i < scatterTableEntries; ++i)
    {
        Vector4f v;
        v.x() = in.readFloat();
        v.y() = in.readFloat();
        v.z() = in.readFloat();
        v.w() = in.readFloat();
        atmosphere->m_inscatterTable[i] = v;
    }
    if (in.status() != InputDataStream::Good)
    {
        VESTA_LOG("Error reading inscatter table in atmscat file.");
        delete atmosphere;
        return NULL;
    }

    return atmosphere;
}

/** Save an atmosphere to a .atmscat file.
  *
  * atmscat file header format:
  *
  * bytes          contents
  * -------------------------------
  * 0-7            header string ("atmscatr")
  * 8-11           version identifier (uint32)
  * 12-15          Rayleigh scale height (float)
  * 16-27          Rayleigh scattering coefficients (3 floats)
  * 28-31          Mie scale height (float)
  * 32-35          Mie scattering coefficient (float)
  * 36-39          Mie asymmetry parameter (float)
  * 40-51          Absorption coefficients (3 floats)
  * 52-55          Planet radius (float)
  * 56-63          Transmittance table dimensions (2 uint32, width * height)
  * 64-75          Scattering table dimensions (3 uint32, width * height * depth)
  *
  * transmittance table (width * height * 3 floats)
  * scattering table (width * height * depth * 4 floats)
  */
void
Atmosphere::SaveAtmScat(const char* filename)
{
    filebuf fb;
    fb.open (filename,ios::out | ios::binary);
    ostream os(&fb);
    OutputDataStream out(os);
    out.setByteOrder(OutputDataStream::LittleEndian);

    out.writeData("atmscatr", 8);

    out.writeInt32(1);
    if (out.status() != OutputDataStream::Good)
    {
        VESTA_LOG("Error writing header of atmscat file.");
        return;
    }

    out.writeFloat(m_rayleighScaleHeight);
    out.writeFloat(m_rayleighScatteringCoeff.x());
    out.writeFloat(m_rayleighScatteringCoeff.y());
    out.writeFloat(m_rayleighScatteringCoeff.z());
    out.writeFloat(m_mieScaleHeight);
    out.writeFloat(m_mieScatteringCoeff);
    out.writeFloat(m_mieAsymmetry);
    out.writeFloat(m_absorptionCoeff.x());
    out.writeFloat(m_absorptionCoeff.y());
    out.writeFloat(m_absorptionCoeff.z());
    out.writeFloat(m_planetRadius);
    out.writeUint32(m_transmittanceViewAngleSamples);
    out.writeUint32(m_transmittanceHeightSamples);
    out.writeUint32(m_scatterSunAngleSamples);
    out.writeUint32(m_scatterViewAngleSamples);
    out.writeUint32(m_scatterHeightSamples);

    if (out.status() != OutputDataStream::Good)
    {
        VESTA_LOG("Error writing header of atmscat file.");
        return;
    }

    for (unsigned int i = 0; i < m_transmittanceTable.size(); ++i)
    {
        out.writeFloat(m_transmittanceTable[i].x());
        out.writeFloat(m_transmittanceTable[i].y());
        out.writeFloat(m_transmittanceTable[i].z());
    }

    if (out.status() != OutputDataStream::Good)
    {
        VESTA_LOG("Error writing transmittance table in atmscat file.");
        return;
    }

    for (unsigned int i = 0; i < m_inscatterTable.size(); ++i)
    {
        out.writeFloat(m_inscatterTable[i].x());
        out.writeFloat(m_inscatterTable[i].y());
        out.writeFloat(m_inscatterTable[i].z());
        out.writeFloat(m_inscatterTable[i].w());
    }
    if (out.status() != OutputDataStream::Good)
    {
        VESTA_LOG("Error reading inscatter table in atmscat file.");
        return;
    }

    fb.close();
}


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

    @copyright 2016 NumScale SAS

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

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

  /*!
    @ingroup group-trigonometric
    This function object returns the cotangent of input in degree:
    \f$\cos(\pi x/180)/\sin(\pi x/180)\f$.

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

    @par Note

      As most other trigonometric function cotd can be called
      with a second optional parameter  which is a tag on speed
      and accuracy (see @ref cos for further details)

    @see cos, sin, tan, cot, cotpi

    @par Example:

      @snippet cotd.cpp cotd

    @par Possible output:

      @snippet cotd.txt cotd

  **/
  IEEEValue cotd(IEEEValue const& x);
} }
#endif

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

#endif