// boost\math\distributions\non_central_t.hpp

// Copyright John Maddock 2008.

// 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_SPECIAL_NON_CENTRAL_T_HPP
#define BOOST_MATH_SPECIAL_NON_CENTRAL_T_HPP

#include <boost/math/distributions/fwd.hpp>
#include <boost/math/distributions/non_central_beta.hpp> // for nc beta
#include <boost/math/distributions/normal.hpp> // for normal CDF and quantile
#include <boost/math/distributions/students_t.hpp>
#include <boost/math/distributions/detail/generic_quantile.hpp> // quantile
#include <boost/math/special_functions/trunc.hpp>
#include <boost/math/special_functions/detail/hypergeometric_series.hpp>
#include <boost/math/quadrature/exp_sinh.hpp>

namespace boost
{
   namespace math
   {

      template <class RealType, class Policy>
      class non_central_t_distribution;

      namespace detail{

         template <class T, class Policy>
         T non_central_t2_p(T v, T delta, T x, T y, const Policy& pol, T init_val)
         {
            BOOST_MATH_STD_USING
            //
            // Variables come first:
            //
            std::uintmax_t max_iter = policies::get_max_series_iterations<Policy>();
            T errtol = policies::get_epsilon<T, Policy>();
            T d2 = delta * delta / 2;
            //
            // k is the starting point for iteration, and is the
            // maximum of the poisson weighting term, we don't
            // ever allow k == 0 as this can lead to catastrophic
            // cancellation errors later (test case is v = 1621286869049072.3
            // delta = 0.16212868690490723, x = 0.86987415482475994).
            //
            long long k = lltrunc(d2);
            T pois;
            if(k == 0) k = 1;
            // Starting Poisson weight:
            pois = gamma_p_derivative(T(k+1), d2, pol)
               * tgamma_delta_ratio(T(k + 1), T(0.5f))
               * delta / constants::root_two<T>();
            if(pois == 0)
               return init_val;
            T xterm, beta;
            // Recurrence & starting beta terms:
            beta = x < y
               ? detail::ibeta_imp(T(k + 1), T(v / 2), x, pol, false, true, &xterm)
               : detail::ibeta_imp(T(v / 2), T(k + 1), y, pol, true, true, &xterm);

            while (fabs(beta * pois) < tools::min_value<T>())
            {
               if ((k == 0) || (pois == 0))
                  return init_val;
               k /= 2;
               pois = gamma_p_derivative(T(k + 1), d2, pol)
                  * tgamma_delta_ratio(T(k + 1), T(0.5f))
                  * delta / constants::root_two<T>();
               beta = x < y
                  ? detail::ibeta_imp(T(k + 1), T(v / 2), x, pol, false, true, &xterm)
                  : detail::ibeta_imp(T(v / 2), T(k + 1), y, pol, true, true, &xterm);
            }

            xterm *= y / (v / 2 + k);
            T poisf(pois), betaf(beta), xtermf(xterm);
            T sum = init_val;
            if((xterm == 0) && (beta == 0))
               return init_val;

            //
            // Backwards recursion first, this is the stable
            // direction for recursion:
            //
            std::uintmax_t count = 0;
            T last_term = 0;
            for(auto i = k; i >= 0; --i)
            {
               T term = beta * pois;
               sum += term;
               // Don't terminate on first term in case we "fixed" k above:
               if(((fabs(last_term) > fabs(term)) && fabs(term/sum) < errtol) || (v == 2 && i == 0))
                  break;
               last_term = term;
               pois *= (i + 0.5f) / d2;
               beta += xterm;
               xterm *= (i) / (x * (v / 2 + i - 1));
               ++count;
            }
            last_term = 0;
            for(auto i = k + 1; ; ++i)
            {
               poisf *= d2 / (i + 0.5f);
               xtermf *= (x * (v / 2 + i - 1)) / (i);
               betaf -= xtermf;
               T term = poisf * betaf;
               sum += term;
               if((fabs(last_term) >= fabs(term)) && (fabs(term/sum) < errtol))
                  break;
               last_term = term;
               ++count;
               if(count > max_iter)
               {
                  return policies::raise_evaluation_error("cdf(non_central_t_distribution<%1%>, %1%)", "Series did not converge, closest value was %1%", sum, pol); // LCOV_EXCL_LINE
               }
            }
            return sum;
         }

         template <class T, class Policy>
         T non_central_t2_q(T v, T delta, T x, T y, const Policy& pol, T init_val)
         {
            BOOST_MATH_STD_USING
            //
            // Variables come first:
            //
            std::uintmax_t max_iter = policies::get_max_series_iterations<Policy>();
            T errtol = boost::math::policies::get_epsilon<T, Policy>();
            T d2 = delta * delta / 2;
            //
            // k is the starting point for iteration, and is the
            // maximum of the poisson weighting term, we don't allow
            // k == 0 as this can cause catastrophic cancellation errors
            // (test case is v = 561908036470413.25, delta = 0.056190803647041321,
            // x = 1.6155232703966216):
            //
            long long k = lltrunc(d2);
            if(k == 0) k = 1;
            // Starting Poisson weight:
            T pois;
            if((k < static_cast<long long>(max_factorial<T>::value)) && (d2 < tools::log_max_value<T>()) && (log(d2) * k < tools::log_max_value<T>()))
            {
               //
               // For small k we can optimise this calculation by using
               // a simpler reduced formula:
               //
               pois = exp(-d2);
               pois *= pow(d2, static_cast<T>(k));
               pois /= boost::math::tgamma(T(k + 1 + 0.5), pol);
               pois *= delta / constants::root_two<T>();
            }
            else
            {
               pois = gamma_p_derivative(T(k+1), d2, pol)
                  * tgamma_delta_ratio(T(k + 1), T(0.5f))
                  * delta / constants::root_two<T>();
            }
            if(pois == 0)
               return init_val;
            // Recurance term:
            T xterm;
            T beta;
            // Starting beta term:
            if(k != 0)
            {
               beta = x < y
                  ? detail::ibeta_imp(T(k + 1), T(v / 2), x, pol, true, true, &xterm)
                  : detail::ibeta_imp(T(v / 2), T(k + 1), y, pol, false, true, &xterm);

               xterm *= y / (v / 2 + k);
            }
            else
            {
               beta = pow(y, v / 2);
               xterm = beta;
            }
            T poisf(pois), betaf(beta), xtermf(xterm);
            T sum = init_val;
            if((xterm == 0) && (beta == 0))
               return init_val;

            //
            // Fused forward and backwards recursion:
            //
            std::uintmax_t count = 0;
            T last_term = 0;
            for(auto i = k + 1, j = k; ; ++i, --j)
            {
               poisf *= d2 / (i + 0.5f);
               xtermf *= (x * (v / 2 + i - 1)) / (i);
               betaf += xtermf;
               T term = poisf * betaf;

               if(j >= 0)
               {
                  term += beta * pois;
                  pois *= (j + 0.5f) / d2;
                  beta -= xterm;
                  if(!(v == 2 && j == 0))
                     xterm *= (j) / (x * (v / 2 + j - 1));
               }

               sum += term;
               // Don't terminate on first term in case we "fixed" the value of k above:
               if((fabs(last_term) > fabs(term)) && fabs(term/sum) < errtol)
                  break;
               last_term = term;
               if(count > max_iter)
               {
                  return policies::raise_evaluation_error("cdf(non_central_t_distribution<%1%>, %1%)", "Series did not converge, closest value was %1%", sum, pol); // LCOV_EXCL_LINE
               }
               ++count;
            }
            return sum;
         }

         template <class T, class Policy>
         T non_central_t_cdf(T v, T delta, T t, bool invert, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            if ((boost::math::isinf)(v))
            { // Infinite degrees of freedom, so use normal distribution located at delta.
               normal_distribution<T, Policy> n(delta, 1);
               return cdf(n, t);
            }
            //
            // Otherwise, for t < 0 we have to use the reflection formula:
            if(t < 0)
            {
               t = -t;
               delta = -delta;
               invert = !invert;
            }
            if(fabs(delta / (4 * v)) < policies::get_epsilon<T, Policy>())
            {
               // Approximate with a Student's T centred on delta,
               // the crossover point is based on eq 2.6 from
               // "A Comparison of Approximations To Percentiles of the
               // Noncentral t-Distribution".  H. Sahai and M. M. Ojeda,
               // Revista Investigacion Operacional Vol 21, No 2, 2000.
               // Original sources referenced in the above are:
               // "Some Approximations to the Percentage Points of the Noncentral
               // t-Distribution". C. van Eeden. International Statistical Review, 29, 4-31.
               // "Continuous Univariate Distributions".  N.L. Johnson, S. Kotz and
               // N. Balkrishnan. 1995. John Wiley and Sons New York.
               T result = cdf(students_t_distribution<T, Policy>(v), t - delta);
               return invert ? 1 - result : result;
            }
            //
            // x and y are the corresponding random
            // variables for the noncentral beta distribution,
            // with y = 1 - x:
            //
            T x = t * t / (v + t * t);
            T y = v / (v + t * t);
            T d2 = delta * delta;
            T a = 0.5f;
            T b = v / 2;
            T c = a + b + d2 / 2;
            //
            // Crossover point for calculating p or q is the same
            // as for the noncentral beta:
            //
            T cross = 1 - (b / c) * (1 + d2 / (2 * c * c));
            T result;
            if(x < cross)
            {
               //
               // Calculate p:
               //
               if(x != 0)
               {
                  result = non_central_beta_p(a, b, d2, x, y, pol);
                  result = non_central_t2_p(v, delta, x, y, pol, result);
                  result /= 2;
               }
               else
                  result = 0;
               if (invert)
               {
                  result = cdf(complement(boost::math::normal_distribution<T, Policy>(), -delta)) - result;
                  invert = false;
               }
               else
                  result += cdf(boost::math::normal_distribution<T, Policy>(), -delta);
            }
            else
            {
               //
               // Calculate q:
               //
               invert = !invert;
               if(x != 0)
               {
                  result = non_central_beta_q(a, b, d2, x, y, pol);
                  result = non_central_t2_q(v, delta, x, y, pol, result);
                  result /= 2;
               }
               else // x == 0
                  result = cdf(complement(boost::math::normal_distribution<T, Policy>(), -delta));
            }
            if(invert)
               result = 1 - result;
            return result;
         }

         template <class T, class Policy>
         T non_central_t_quantile(const char* function, T v, T delta, T p, T q, const Policy&)
         {
            BOOST_MATH_STD_USING
     //       static const char* function = "quantile(non_central_t_distribution<%1%>, %1%)";
     // now passed as function
            typedef typename policies::evaluation<T, Policy>::type value_type;
            typedef typename policies::normalise<
               Policy,
               policies::promote_float<false>,
               policies::promote_double<false>,
               policies::discrete_quantile<>,
               policies::assert_undefined<> >::type forwarding_policy;

               T r;
               if(!detail::check_df_gt0_to_inf(
                  function,
                  v, &r, Policy())
                  ||
               !detail::check_non_centrality(
                  function,
                  static_cast<T>(delta * delta),
                  &r,
                  Policy())
                  ||
               !detail::check_probability(
                  function,
                  p,
                  &r,
                  Policy()))
                     return r;


            value_type guess = 0;
            if ( ((boost::math::isinf)(v)) || (v > 1 / boost::math::tools::epsilon<T>()) )
            { // Infinite or very large degrees of freedom, so use normal distribution located at delta.
               normal_distribution<T, Policy> n(delta, 1);
               if (p < q)
               {
                 return quantile(n, p);
               }
               else
               {
                 return quantile(complement(n, q));
               }
            }
            else if(v > 3)
            { // Use normal distribution to calculate guess.
               value_type mean = (v > 1 / policies::get_epsilon<T, Policy>()) ? delta : delta * sqrt(v / 2) * tgamma_delta_ratio((v - 1) * 0.5f, T(0.5f));
               value_type var = (v > 1 / policies::get_epsilon<T, Policy>()) ? value_type(1) : (((delta * delta + 1) * v) / (v - 2) - mean * mean);
               if(p < q)
                  guess = quantile(normal_distribution<value_type, forwarding_policy>(mean, var), p);
               else
                  guess = quantile(complement(normal_distribution<value_type, forwarding_policy>(mean, var), q));
            }
            //
            // We *must* get the sign of the initial guess correct,
            // or our root-finder will fail, so double check it now:
            //
            value_type pzero = non_central_t_cdf(
               static_cast<value_type>(v),
               static_cast<value_type>(delta),
               static_cast<value_type>(0),
               !(p < q),
               forwarding_policy());
            int s;
            if(p < q)
               s = boost::math::sign(p - pzero);
            else
               s = boost::math::sign(pzero - q);
            if(s != boost::math::sign(guess))
            {
               guess = static_cast<T>(s);
            }

            value_type result = detail::generic_quantile(
               non_central_t_distribution<value_type, forwarding_policy>(v, delta),
               (p < q ? p : q),
               guess,
               (p >= q),
               function);
            return policies::checked_narrowing_cast<T, forwarding_policy>(
               result,
               function);
         }

         template <class T, class Policy>
         T non_central_t_pdf_integral(T x, T v, T mu, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            boost::math::quadrature::exp_sinh<T, Policy> integrator;
            T integral = pow(v, v / 2) * exp(-v * mu * mu / (2 * (x * x + v)));
            if (integral != 0)
            {
               integral *= integrator.integrate([&x, v, mu](T y) 
                  {
                     T p;
                     if (v * log(y) < tools::log_max_value<T>())
                        p = pow(y, v) * exp(boost::math::pow<2>((y - mu * x / sqrt(x * x + v))) / -2);
                     else
                        p = exp(log(y) * v + boost::math::pow<2>((y - mu * x / sqrt(x * x + v))) / -2);
                     return p; 
                  });
            }
            integral /= boost::math::constants::root_pi<T>() * boost::math::tgamma(v / 2, pol) * pow(T(2), (v - 1) / 2) * pow(x * x + v, (v + 1) / 2);
            return integral;
         }

         template <class T, class Policy>
         T non_central_t_pdf_hypergeometric(T x, T v, T mu, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            long long scale = 0;
            const char* function = "non central T PDF";
            //
            // We only call this routine when we know that the series form of 1F1 is cheap to evaluate,
            // so no need to call the whole 1F1 function, just the series will do:
            //
            T Av = hypergeometric_1F1_generic_series(static_cast<T>((v + 1) / 2), boost::math::constants::half<T>(), static_cast<T>(mu * mu * x * x / (2 * (x * x + v))), pol, scale, function);
            Av = ldexp(Av, static_cast<int>(scale));
            scale = 0;
            T Bv = hypergeometric_1F1_generic_series(static_cast<T>(v / 2 + T(1)), static_cast<T>(T(3) / 2), static_cast<T>(mu * mu * x * x / (2 * (x * x + v))), pol, scale, function);
            Bv = ldexp(Bv, static_cast<int>(scale));
            Bv *= boost::math::tgamma_delta_ratio(v / 2 + T(1), -constants::half<T>(), pol);
            Bv *= boost::math::constants::root_two<T>() * mu * x / sqrt(x * x + v);

            T tolerance = tools::root_epsilon<T>() * Av * 4;
            Av += Bv;

            if (Av < tolerance)
            {
               // More than half the digits have cancelled, fall back to the integral method:
               return non_central_t_pdf_integral(x, v, mu, pol);
            }

            Av *= exp(-mu * mu / 2) * pow(1 + x * x / v, -(v + 1) / 2) * boost::math::tgamma_delta_ratio(v / 2 + constants::half<T>(), -constants::half<T>(), pol);
            Av /= sqrt(v) * boost::math::constants::root_pi<T>();
            return Av;
         }

         template <class T, class Policy>
         T non_central_t2_pdf(T n, T delta, T x, T y, const Policy& pol, T init_val)
         {
            BOOST_MATH_STD_USING
            //
            // Variables come first:
            //
            std::uintmax_t max_iter = policies::get_max_series_iterations<Policy>();
            T errtol = boost::math::policies::get_epsilon<T, Policy>();
            T d2 = delta * delta / 2;
            //
            // k is the starting point for iteration, and is the
            // maximum of the poisson weighting term:
            //
            long long k = lltrunc(d2);
            T pois, xterm;
            if(k == 0)
               k = 1;
            // Starting Poisson weight:
            pois = gamma_p_derivative(T(k+1), d2, pol)
               * tgamma_delta_ratio(T(k + 1), T(0.5f))
               * delta / constants::root_two<T>();
            BOOST_MATH_INSTRUMENT_VARIABLE(pois);
            // Starting beta term:
            xterm = x < y
               ? ibeta_derivative(T(k + 1), n / 2, x, pol)
               : ibeta_derivative(n / 2, T(k + 1), y, pol);
            BOOST_MATH_INSTRUMENT_VARIABLE(xterm);

            while (fabs(xterm * pois) < tools::min_value<T>())
            {
               if (k == 0)
                  return init_val;
               k /= 2;
               pois = gamma_p_derivative(T(k + 1), d2, pol)
                  * tgamma_delta_ratio(T(k + 1), T(0.5f))
                  * delta / constants::root_two<T>();
               BOOST_MATH_INSTRUMENT_VARIABLE(pois);
               // Starting beta term:
               xterm = x < y
                  ? ibeta_derivative(T(k + 1), n / 2, x, pol)
                  : ibeta_derivative(n / 2, T(k + 1), y, pol);
               BOOST_MATH_INSTRUMENT_VARIABLE(xterm);
            }

            T poisf(pois), xtermf(xterm);
            T sum = init_val;

            //
            // Backwards recursion first, this is the stable
            // direction for recursion:
            //
            std::uintmax_t count = 0;
            T old_ratio = 1;  // arbitrary "large" value
            for(auto i = k; i >= 0; --i)
            {
               T term = xterm * pois;
               sum += term;
               BOOST_MATH_INSTRUMENT_VARIABLE(sum);
               T ratio = fabs(term / sum);
               if(((ratio < errtol) && (i != k) && (ratio < old_ratio)) || (term == 0))
                  break;
               old_ratio = ratio;
               pois *= (i + 0.5f) / d2;
               xterm *= (i) / (x * (n / 2 + i));
               ++count;
               if(count > max_iter)
               {
                  return policies::raise_evaluation_error("pdf(non_central_t_distribution<%1%>, %1%)", "Series did not converge, closest value was %1%", sum, pol); // LCOV_EXCL_LINE
               }
            }
            BOOST_MATH_INSTRUMENT_VARIABLE(sum);
            old_ratio = 0;
            for(auto i = k + 1; ; ++i)
            {
               poisf *= d2 / (i + 0.5f);
               xtermf *= (x * (n / 2 + i)) / (i);
               T term = poisf * xtermf;
               sum += term;
               T ratio = fabs(term / sum);
               if(((ratio < errtol) && (ratio < old_ratio)) || (term == 0))
                  break;
               ++count;
               old_ratio = ratio;
               if(count > max_iter)
               {
                  return policies::raise_evaluation_error("pdf(non_central_t_distribution<%1%>, %1%)", "Series did not converge, closest value was %1%", sum, pol); // LCOV_EXCL_LINE
               }
            }
            BOOST_MATH_INSTRUMENT_VARIABLE(sum);
            return sum;
         }

         template <class T, class Policy>
         T non_central_t_pdf(T n, T delta, T t, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            if ((boost::math::isinf)(n))
            { // Infinite degrees of freedom, so use normal distribution located at delta.
               normal_distribution<T, Policy> norm(delta, 1);
               return pdf(norm, t);
            }
            if(t * t < tools::epsilon<T>())
            {
               //
               // Handle this as a special case, using the formula
               // from Weisstein, Eric W.
               // "Noncentral Student's t-Distribution."
               // From MathWorld--A Wolfram Web Resource.
               // http://mathworld.wolfram.com/NoncentralStudentst-Distribution.html
               //
               // The formula is simplified thanks to the relation
               // 1F1(a,b,0) = 1.
               //
               return tgamma_delta_ratio(n / 2 + 0.5f, T(0.5f))
                  * sqrt(n / constants::pi<T>())
                  * exp(-delta * delta / 2) / 2;
            }
            if(fabs(delta / (4 * n)) < policies::get_epsilon<T, Policy>())
            {
               // Approximate with a Student's T centred on delta,
               // the crossover point is based on eq 2.6 from
               // "A Comparison of Approximations To Percentiles of the
               // Noncentral t-Distribution".  H. Sahai and M. M. Ojeda,
               // Revista Investigacion Operacional Vol 21, No 2, 2000.
               // Original sources referenced in the above are:
               // "Some Approximations to the Percentage Points of the Noncentral
               // t-Distribution". C. van Eeden. International Statistical Review, 29, 4-31.
               // "Continuous Univariate Distributions".  N.L. Johnson, S. Kotz and
               // N. Balkrishnan. 1995. John Wiley and Sons New York.
               return pdf(students_t_distribution<T, Policy>(n), t - delta);
            }
            //
            // Figure out if the hypergeometric formula will be efficient or not, 
            // based on where the summit of the series.
            //
            T a = (n + 1) / 2;
            T x = delta * delta * t * t / (2 * (t * t + n));
            T summit = (sqrt(x * (4 * a + x)) + x) / 2;
            if (summit < 40)
               return non_central_t_pdf_hypergeometric(t, n, delta, pol);
            // 
            // Otherwise, for t < 0 we have to use the reflection formula:
            //
            if (t < 0)
            {
               t = -t;
               delta = -delta;
            }
            //
            // x and y are the corresponding random
            // variables for the noncentral beta distribution,
            // with y = 1 - x:
            //
            x = t * t / (n + t * t);
            T y = n / (n + t * t);
            a = 0.5f;
            T b = n / 2;
            T d2 = delta * delta;
            //
            // Calculate pdf:
            //
            T dt = n * t / (n * n + 2 * n * t * t + t * t * t * t);
            BOOST_MATH_INSTRUMENT_VARIABLE(dt);
            T result = non_central_beta_pdf(a, b, d2, x, y, pol);
            BOOST_MATH_INSTRUMENT_VARIABLE(result);
            T tol = tools::root_epsilon<T>() * result;
            result = non_central_t2_pdf(n, delta, x, y, pol, result);
            BOOST_MATH_INSTRUMENT_VARIABLE(result);
            result *= dt;
            if (result <= tol)
            {
               // More than half the digits in the result have cancelled, 
               // Try direct integration... super slow but reliable as far as we can tell...
               if (delta < 0)
               {
                  // reflect back:
                  delta = -delta;
                  t = -t;
               }
               result = non_central_t_pdf_integral(t, n, delta, pol);
            }
            return result;
         }

         template <class T, class Policy>
         T mean(T v, T delta, const Policy& pol)
         {
            if ((boost::math::isinf)(v))
            {
               return delta;
            }
            BOOST_MATH_STD_USING
            if (v > 1 / boost::math::tools::epsilon<T>() )
            {
              //normal_distribution<T, Policy> n(delta, 1);
              //return boost::math::mean(n);
              return delta;
            }
            else
            {
             return delta * sqrt(v / 2) * tgamma_delta_ratio((v - 1) * 0.5f, T(0.5f), pol);
            }
            // Other moments use mean so using normal distribution is propagated.
         }

         template <class T, class Policy>
         T variance(T v, T delta, const Policy& pol)
         {
            if ((boost::math::isinf)(v))
            {
               return 1;
            }
            if (delta == 0)
            {  // == Student's t
              return v / (v - 2);
            }
            T result = ((delta * delta + 1) * v) / (v - 2);
            T m = mean(v, delta, pol);
            result -= m * m;
            return result;
         }

         template <class T, class Policy>
         T skewness(T v, T delta, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            if ((boost::math::isinf)(v))
            {
               return 0;
            }
            if(delta == 0)
            { // == Student's t
              return 0;
            }
            T mean = boost::math::detail::mean(v, delta, pol);
            T l2 = delta * delta;
            T var = ((l2 + 1) * v) / (v - 2) - mean * mean;
            T result = -2 * var;
            result += v * (l2 + 2 * v - 3) / ((v - 3) * (v - 2));
            result *= mean;
            result /= pow(var, T(1.5f));
            return result;
         }

         template <class T, class Policy>
         T kurtosis_excess(T v, T delta, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            if ((boost::math::isinf)(v))
            {
               return 1;
            }
            if (delta == 0)
            { // == Student's t
              return 1;
            }
            T mean = boost::math::detail::mean(v, delta, pol);
            T l2 = delta * delta;
            T var = ((l2 + 1) * v) / (v - 2) - mean * mean;
            T result = -3 * var;
            result += v * (l2 * (v + 1) + 3 * (3 * v - 5)) / ((v - 3) * (v - 2));
            result *= -mean * mean;
            result += v * v * (l2 * l2 + 6 * l2 + 3) / ((v - 4) * (v - 2));
            result /= var * var;
            result -= static_cast<T>(3);
            return result;
         }

#if 0
         //
         // This code is disabled, since there can be multiple answers to the
         // question, and it's not clear how to find the "right" one.
         //
         template <class RealType, class Policy>
         struct t_degrees_of_freedom_finder
         {
            t_degrees_of_freedom_finder(
               RealType delta_, RealType x_, RealType p_, bool c)
               : delta(delta_), x(x_), p(p_), comp(c) {}

            RealType operator()(const RealType& v)
            {
               non_central_t_distribution<RealType, Policy> d(v, delta);
               return comp ?
                  p - cdf(complement(d, x))
                  : cdf(d, x) - p;
            }
         private:
            RealType delta;
            RealType x;
            RealType p;
            bool comp;
         };

         template <class RealType, class Policy>
         inline RealType find_t_degrees_of_freedom(
            RealType delta, RealType x, RealType p, RealType q, const Policy& pol)
         {
            const char* function = "non_central_t<%1%>::find_degrees_of_freedom";
            if((p == 0) || (q == 0))
            {
               //
               // Can't a thing if one of p and q is zero:
               //
               return policies::raise_evaluation_error<RealType>(function, "Can't find degrees of freedom when the probability is 0 or 1, only possible answer is %1%", // LCOV_EXCL_LINE
                  RealType(std::numeric_limits<RealType>::quiet_NaN()), Policy()); // LCOV_EXCL_LINE
            }
            t_degrees_of_freedom_finder<RealType, Policy> f(delta, x, p < q ? p : q, p < q ? false : true);
            tools::eps_tolerance<RealType> tol(policies::digits<RealType, Policy>());
            std::uintmax_t max_iter = policies::get_max_root_iterations<Policy>();
            //
            // Pick an initial guess:
            //
            RealType guess = 200;
            std::pair<RealType, RealType> ir = tools::bracket_and_solve_root(
               f, guess, RealType(2), false, tol, max_iter, pol);
            RealType result = ir.first + (ir.second - ir.first) / 2;
            if(max_iter >= policies::get_max_root_iterations<Policy>())
            {
               return policies::raise_evaluation_error<RealType>(function, "Unable to locate solution in a reasonable time:" // LCOV_EXCL_LINE
                  " or there is no answer to problem.  Current best guess is %1%", result, Policy()); // LCOV_EXCL_LINE
            }
            return result;
         }

         template <class RealType, class Policy>
         struct t_non_centrality_finder
         {
            t_non_centrality_finder(
               RealType v_, RealType x_, RealType p_, bool c)
               : v(v_), x(x_), p(p_), comp(c) {}

            RealType operator()(const RealType& delta)
            {
               non_central_t_distribution<RealType, Policy> d(v, delta);
               return comp ?
                  p - cdf(complement(d, x))
                  : cdf(d, x) - p;
            }
         private:
            RealType v;
            RealType x;
            RealType p;
            bool comp;
         };

         template <class RealType, class Policy>
         inline RealType find_t_non_centrality(
            RealType v, RealType x, RealType p, RealType q, const Policy& pol)
         {
            const char* function = "non_central_t<%1%>::find_t_non_centrality";
            if((p == 0) || (q == 0))
            {
               //
               // Can't do a thing if one of p and q is zero:
               //
               return policies::raise_evaluation_error<RealType>(function, "Can't find non-centrality parameter when the probability is 0 or 1, only possible answer is %1%", // LCOV_EXCL_LINE
                  RealType(std::numeric_limits<RealType>::quiet_NaN()), Policy()); // LCOV_EXCL_LINE
            }
            t_non_centrality_finder<RealType, Policy> f(v, x, p < q ? p : q, p < q ? false : true);
            tools::eps_tolerance<RealType> tol(policies::digits<RealType, Policy>());
            std::uintmax_t max_iter = policies::get_max_root_iterations<Policy>();
            //
            // Pick an initial guess that we know is the right side of
            // zero:
            //
            RealType guess;
            if(f(0) < 0)
               guess = 1;
            else
               guess = -1;
            std::pair<RealType, RealType> ir = tools::bracket_and_solve_root(
               f, guess, RealType(2), false, tol, max_iter, pol);
            RealType result = ir.first + (ir.second - ir.first) / 2;
            if(max_iter >= policies::get_max_root_iterations<Policy>())
            {
               return policies::raise_evaluation_error<RealType>(function, "Unable to locate solution in a reasonable time:" // LCOV_EXCL_LINE
                  " or there is no answer to problem.  Current best guess is %1%", result, Policy()); // LCOV_EXCL_LINE
            }
            return result;
         }
#endif
      } // namespace detail ======================================================================

      template <class RealType = double, class Policy = policies::policy<> >
      class non_central_t_distribution
      {
      public:
         typedef RealType value_type;
         typedef Policy policy_type;

         non_central_t_distribution(RealType v_, RealType lambda) : v(v_), ncp(lambda)
         {
            const char* function = "boost::math::non_central_t_distribution<%1%>::non_central_t_distribution(%1%,%1%)";
            RealType r;
            detail::check_df_gt0_to_inf(
               function,
               v, &r, Policy());
            detail::check_non_centrality(
               function,
               static_cast<value_type>(lambda * lambda),
               &r,
               Policy());
         } // non_central_t_distribution constructor.

         RealType degrees_of_freedom() const
         { // Private data getter function.
            return v;
         }
         RealType non_centrality() const
         { // Private data getter function.
            return ncp;
         }
#if 0
         //
         // This code is disabled, since there can be multiple answers to the
         // question, and it's not clear how to find the "right" one.
         //
         static RealType find_degrees_of_freedom(RealType delta, RealType x, RealType p)
         {
            const char* function = "non_central_t<%1%>::find_degrees_of_freedom";
            typedef typename policies::evaluation<RealType, Policy>::type value_type;
            typedef typename policies::normalise<
               Policy,
               policies::promote_float<false>,
               policies::promote_double<false>,
               policies::discrete_quantile<>,
               policies::assert_undefined<> >::type forwarding_policy;
            value_type result = detail::find_t_degrees_of_freedom(
               static_cast<value_type>(delta),
               static_cast<value_type>(x),
               static_cast<value_type>(p),
               static_cast<value_type>(1-p),
               forwarding_policy());
            return policies::checked_narrowing_cast<RealType, forwarding_policy>(
               result,
               function);
         }
         template <class A, class B, class C>
         static RealType find_degrees_of_freedom(const complemented3_type<A,B,C>& c)
         {
            const char* function = "non_central_t<%1%>::find_degrees_of_freedom";
            typedef typename policies::evaluation<RealType, Policy>::type value_type;
            typedef typename policies::normalise<
               Policy,
               policies::promote_float<false>,
               policies::promote_double<false>,
               policies::discrete_quantile<>,
               policies::assert_undefined<> >::type forwarding_policy;
            value_type result = detail::find_t_degrees_of_freedom(
               static_cast<value_type>(c.dist),
               static_cast<value_type>(c.param1),
               static_cast<value_type>(1-c.param2),
               static_cast<value_type>(c.param2),
               forwarding_policy());
            return policies::checked_narrowing_cast<RealType, forwarding_policy>(
               result,
               function);
         }
         static RealType find_non_centrality(RealType v, RealType x, RealType p)
         {
            const char* function = "non_central_t<%1%>::find_t_non_centrality";
            typedef typename policies::evaluation<RealType, Policy>::type value_type;
            typedef typename policies::normalise<
               Policy,
               policies::promote_float<false>,
               policies::promote_double<false>,
               policies::discrete_quantile<>,
               policies::assert_undefined<> >::type forwarding_policy;
            value_type result = detail::find_t_non_centrality(
               static_cast<value_type>(v),
               static_cast<value_type>(x),
               static_cast<value_type>(p),
               static_cast<value_type>(1-p),
               forwarding_policy());
            return policies::checked_narrowing_cast<RealType, forwarding_policy>(
               result,
               function);
         }
         template <class A, class B, class C>
         static RealType find_non_centrality(const complemented3_type<A,B,C>& c)
         {
            const char* function = "non_central_t<%1%>::find_t_non_centrality";
            typedef typename policies::evaluation<RealType, Policy>::type value_type;
            typedef typename policies::normalise<
               Policy,
               policies::promote_float<false>,
               policies::promote_double<false>,
               policies::discrete_quantile<>,
               policies::assert_undefined<> >::type forwarding_policy;
            value_type result = detail::find_t_non_centrality(
               static_cast<value_type>(c.dist),
               static_cast<value_type>(c.param1),
               static_cast<value_type>(1-c.param2),
               static_cast<value_type>(c.param2),
               forwarding_policy());
            return policies::checked_narrowing_cast<RealType, forwarding_policy>(
               result,
               function);
         }
#endif
      private:
         // Data member, initialized by constructor.
         RealType v;   // degrees of freedom
         RealType ncp; // non-centrality parameter
      }; // template <class RealType, class Policy> class non_central_t_distribution

      typedef non_central_t_distribution<double> non_central_t; // Reserved name of type double.

      #ifdef __cpp_deduction_guides
      template <class RealType>
      non_central_t_distribution(RealType,RealType)->non_central_t_distribution<typename boost::math::tools::promote_args<RealType>::type>;
      #endif

      // Non-member functions to give properties of the distribution.

      template <class RealType, class Policy>
      inline const std::pair<RealType, RealType> range(const non_central_t_distribution<RealType, Policy>& /* dist */)
      { // Range of permissible values for random variable k.
         using boost::math::tools::max_value;
         return std::pair<RealType, RealType>(-max_value<RealType>(), max_value<RealType>());
      }

      template <class RealType, class Policy>
      inline const std::pair<RealType, RealType> support(const non_central_t_distribution<RealType, Policy>& /* dist */)
      { // Range of supported values for random variable k.
         // This is range where cdf rises from 0 to 1, and outside it, the pdf is zero.
         using boost::math::tools::max_value;
         return std::pair<RealType, RealType>(-max_value<RealType>(), max_value<RealType>());
      }

      template <class RealType, class Policy>
      inline RealType mode(const non_central_t_distribution<RealType, Policy>& dist)
      { // mode.
         static const char* function = "mode(non_central_t_distribution<%1%> const&)";
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy()))
               return static_cast<RealType>(r);

         BOOST_MATH_STD_USING

         RealType m = v < 3 ? 0 : detail::mean(v, l, Policy());
         RealType var = v < 4 ? 1 : detail::variance(v, l, Policy());

         return detail::generic_find_mode(
            dist,
            m,
            function,
            sqrt(var));
      }

      template <class RealType, class Policy>
      inline RealType mean(const non_central_t_distribution<RealType, Policy>& dist)
      {
         BOOST_MATH_STD_USING
         const char* function = "mean(const non_central_t_distribution<%1%>&)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy()))
               return static_cast<RealType>(r);
         if(v <= 1)
            return policies::raise_domain_error<RealType>(
               function,
               "The non-central t distribution has no defined mean for degrees of freedom <= 1: got v=%1%.", v, Policy());
         // return l * sqrt(v / 2) * tgamma_delta_ratio((v - 1) * 0.5f, RealType(0.5f));
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::mean(static_cast<value_type>(v), static_cast<value_type>(l), forwarding_policy()), function);

      } // mean

      template <class RealType, class Policy>
      inline RealType variance(const non_central_t_distribution<RealType, Policy>& dist)
      { // variance.
         const char* function = "variance(const non_central_t_distribution<%1%>&)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;
         BOOST_MATH_STD_USING
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy()))
               return static_cast<RealType>(r);
         if(v <= 2)
            return policies::raise_domain_error<RealType>(
               function,
               "The non-central t distribution has no defined variance for degrees of freedom <= 2: got v=%1%.", v, Policy());
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::variance(static_cast<value_type>(v), static_cast<value_type>(l), forwarding_policy()), function);
      }

      // RealType standard_deviation(const non_central_t_distribution<RealType, Policy>& dist)
      // standard_deviation provided by derived accessors.

      template <class RealType, class Policy>
      inline RealType skewness(const non_central_t_distribution<RealType, Policy>& dist)
      { // skewness = sqrt(l).
         const char* function = "skewness(const non_central_t_distribution<%1%>&)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy()))
               return static_cast<RealType>(r);
         if(v <= 3)
            return policies::raise_domain_error<RealType>(
               function,
               "The non-central t distribution has no defined skewness for degrees of freedom <= 3: got v=%1%.", v, Policy());;
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::skewness(static_cast<value_type>(v), static_cast<value_type>(l), forwarding_policy()), function);
      }

      template <class RealType, class Policy>
      inline RealType kurtosis_excess(const non_central_t_distribution<RealType, Policy>& dist)
      {
         const char* function = "kurtosis_excess(const non_central_t_distribution<%1%>&)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy()))
               return static_cast<RealType>(r);
         if(v <= 4)
            return policies::raise_domain_error<RealType>(
               function,
               "The non-central t distribution has no defined kurtosis for degrees of freedom <= 4: got v=%1%.", v, Policy());;
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::kurtosis_excess(static_cast<value_type>(v), static_cast<value_type>(l), forwarding_policy()), function);
      } // kurtosis_excess

      template <class RealType, class Policy>
      inline RealType kurtosis(const non_central_t_distribution<RealType, Policy>& dist)
      {
         return kurtosis_excess(dist) + 3;
      }

      template <class RealType, class Policy>
      inline RealType pdf(const non_central_t_distribution<RealType, Policy>& dist, const RealType& t)
      { // Probability Density/Mass Function.
         const char* function = "pdf(non_central_t_distribution<%1%>, %1%)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;

         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l), // we need l^2 to be countable.
            &r,
            Policy())
            ||
         !detail::check_x(
            function,
            t,
            &r,
            Policy()))
               return static_cast<RealType>(r);
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::non_central_t_pdf(static_cast<value_type>(v),
               static_cast<value_type>(l),
               static_cast<value_type>(t),
               Policy()),
            function);
      } // pdf

      template <class RealType, class Policy>
      RealType cdf(const non_central_t_distribution<RealType, Policy>& dist, const RealType& x)
      {
         const char* function = "boost::math::cdf(non_central_t_distribution<%1%>&, %1%)";
//   was const char* function = "boost::math::non_central_t_distribution<%1%>::cdf(%1%)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;

         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy())
            ||
         !detail::check_x(
            function,
            x,
            &r,
            Policy()))
               return static_cast<RealType>(r);
         if ((boost::math::isinf)(v))
          { // Infinite degrees of freedom, so use normal distribution located at delta.
             normal_distribution<RealType, Policy> n(l, 1);
             cdf(n, x);
              //return cdf(normal_distribution<RealType, Policy>(l, 1), x);
          }

         if(l == 0)
         { // NO non-centrality, so use Student's t instead.
            return cdf(students_t_distribution<RealType, Policy>(v), x);
         }
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::non_central_t_cdf(
               static_cast<value_type>(v),
               static_cast<value_type>(l),
               static_cast<value_type>(x),
               false, Policy()),
            function);
      } // cdf

      template <class RealType, class Policy>
      RealType cdf(const complemented2_type<non_central_t_distribution<RealType, Policy>, RealType>& c)
      { // Complemented Cumulative Distribution Function
  // was       const char* function = "boost::math::non_central_t_distribution<%1%>::cdf(%1%)";
         const char* function = "boost::math::cdf(const complement(non_central_t_distribution<%1%>&), %1%)";
         typedef typename policies::evaluation<RealType, Policy>::type value_type;
         typedef typename policies::normalise<
            Policy,
            policies::promote_float<false>,
            policies::promote_double<false>,
            policies::discrete_quantile<>,
            policies::assert_undefined<> >::type forwarding_policy;

         non_central_t_distribution<RealType, Policy> const& dist = c.dist;
         RealType x = c.param;
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality(); // aka delta
         RealType r;
         if(!detail::check_df_gt0_to_inf(
            function,
            v, &r, Policy())
            ||
         !detail::check_non_centrality(
            function,
            static_cast<RealType>(l * l),
            &r,
            Policy())
            ||
         !detail::check_x(
            function,
            x,
            &r,
            Policy()))
               return static_cast<RealType>(r);

         if ((boost::math::isinf)(v))
         { // Infinite degrees of freedom, so use normal distribution located at delta.
             normal_distribution<RealType, Policy> n(l, 1);
             return cdf(complement(n, x));
         }
         if(l == 0)
         { // zero non-centrality so use Student's t distribution.
            return cdf(complement(students_t_distribution<RealType, Policy>(v), x));
         }
         return policies::checked_narrowing_cast<RealType, forwarding_policy>(
            detail::non_central_t_cdf(
               static_cast<value_type>(v),
               static_cast<value_type>(l),
               static_cast<value_type>(x),
               true, Policy()),
            function);
      } // ccdf

      template <class RealType, class Policy>
      inline RealType quantile(const non_central_t_distribution<RealType, Policy>& dist, const RealType& p)
      { // Quantile (or Percent Point) function.
         static const char* function = "quantile(const non_central_t_distribution<%1%>, %1%)";
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         return detail::non_central_t_quantile(function, v, l, p, RealType(1-p), Policy());
      } // quantile

      template <class RealType, class Policy>
      inline RealType quantile(const complemented2_type<non_central_t_distribution<RealType, Policy>, RealType>& c)
      { // Quantile (or Percent Point) function.
         static const char* function = "quantile(const complement(non_central_t_distribution<%1%>, %1%))";
         non_central_t_distribution<RealType, Policy> const& dist = c.dist;
         RealType q = c.param;
         RealType v = dist.degrees_of_freedom();
         RealType l = dist.non_centrality();
         return detail::non_central_t_quantile(function, v, l, RealType(1-q), q, Policy());
      } // quantile complement.

   } // namespace math
} // namespace boost

// This include must be at the end, *after* the accessors
// for this distribution have been defined, in order to
// keep compilers that support two-phase lookup happy.
#include <boost/math/distributions/detail/derived_accessors.hpp>

#endif // BOOST_MATH_SPECIAL_NON_CENTRAL_T_HPP

