// Written in the D programming language.

/**
 * Mathematical Special Functions
 *
 * The technical term 'Special Functions' includes several families of
 * transcendental functions, which have important applications in particular
 * branches of mathematics and physics.
 *
 * The gamma and related functions, and the error function are crucial for
 * mathematical statistics.
 * The Bessel and related functions arise in problems involving wave propagation
 * (especially in optics).
 * Other major categories of special functions include the elliptic integrals
 * (related to the arc length of an ellipse), and the hypergeometric functions.
 *
 * Status:
 *  Many more functions will be added to this module.
 *  The naming convention for the distribution functions (gammaIncomplete, etc)
 *  is not yet finalized and will probably change.
 *
 * Macros:
 *      TABLE_SV = <table border="1" cellpadding="4" cellspacing="0">
 *              <caption>Special Values</caption>
 *              $0</table>
 *      SVH = $(TR $(TH $1) $(TH $2))
 *      SV  = $(TR $(TD $1) $(TD $2))
 *
 *      NAN = $(RED NAN)
 *      SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
 *      GAMMA = &#915;
 *      PSI = &Psi;
 *      THETA = &theta;
 *      INTEGRAL = &#8747;
 *      INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
 *      POWER = $1<sup>$2</sup>
 *      SUB = $1<sub>$2</sub>
 *      BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
 *      CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
 *      CEIL = &#8968;$1&#8969;
 *      PLUSMN = &plusmn;
 *      MNPLUS = &mnplus;
 *      INFIN = &infin;
 *      PLUSMNINF = &plusmn;&infin;
 *      MNPLUSINF = &mnplus;&infin;
 *      PI = &pi;
 *      LT = &lt;
 *      LE = &le;
 *      GT = &gt;
 *      SQRT = &radic;
 *      HALF = &frac12;
 *      COMPLEX = &#8450;
 *
 * Copyright: Based on the CEPHES math library, which is
 *            Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com).
 * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Stephen L. Moshier (original C code). Conversion to D by Don Clugston
 * Source:    $(PHOBOSSRC std/mathspecial.d)
 */
module std.mathspecial;
import std.internal.math.errorfunction;
import std.internal.math.gammafunction;
public import std.math;

/* ***********************************************
 *            GAMMA AND RELATED FUNCTIONS        *
 * ***********************************************/

pure:
nothrow:
@safe:
@nogc:

/** The Gamma function, $(GAMMA)(x)
 *
 *  $(GAMMA)(x) is a generalisation of the factorial function
 *  to real and complex numbers.
 *  Like x!, $(GAMMA)(x+1) = x * $(GAMMA)(x).
 *
 *  Mathematically, if z.re > 0 then
 *   $(GAMMA)(z) = $(INTEGRATE 0, $(INFIN)) $(POWER t, z-1)$(POWER e, -t) dt
 *
 *  $(TABLE_SV
 *    $(SVH  x,           $(GAMMA)(x) )
 *    $(SV  $(NAN),       $(NAN)      )
 *    $(SV  $(PLUSMN)0.0, $(PLUSMNINF))
 *    $(SV integer > 0,   (x-1)!      )
 *    $(SV integer < 0,   $(NAN)      )
 *    $(SV +$(INFIN),      +$(INFIN)   )
 *    $(SV -$(INFIN),      $(NAN)      )
 *  )
 */
real gamma(real x)
{
    return std.internal.math.gammafunction.gamma(x);
}

/** Natural logarithm of the gamma function, $(GAMMA)(x)
 *
 * Returns the base e (2.718...) logarithm of the absolute
 * value of the gamma function of the argument.
 *
 * For reals, logGamma is equivalent to log(fabs(gamma(x))).
 *
 *  $(TABLE_SV
 *    $(SVH  x,             logGamma(x)   )
 *    $(SV  $(NAN),         $(NAN)      )
 *    $(SV integer <= 0,    +$(INFIN)    )
 *    $(SV $(PLUSMNINF),    +$(INFIN)    )
 *  )
 */
real logGamma(real x)
{
    return std.internal.math.gammafunction.logGamma(x);
}

/** The sign of $(GAMMA)(x).
 *
 * Params:
 *   x = the argument of $(GAMMA)
 *
 * Returns:
 *   -1 if $(GAMMA)(x) < 0, +1 if $(GAMMA)(x) > 0, and $(NAN) if $(GAMMA)(x)
 *   does not exist.
 *
 * Note:
 *   This function can be used in conjunction with `logGamma` to evaluate
 *   $(GAMMA)(x) when `gamma(x)` is too large to be represented as a `real`.
 */
pragma(inline, true) real sgnGamma(real x)
{
    return std.internal.math.gammafunction.sgnGamma(x);
}

///
@safe unittest
{
    assert(sgnGamma(10_000) == 1);
}

/** Beta function, B(x,y)
 *
 * Mathematically, if x $(GT) 0 and y $(GT) 0 then
 * B(x,y) = $(INTEGRATE 0, 1)$(POWER t, x-1)$(POWER (l-t), y-1)dt. Through analytic continuation, it
 * is extended to $(COMPLEX)$(SUP 2) where it can be expressed in terms of $(GAMMA)(z).
 *
 * B(x,y) = $(GAMMA)(x)$(GAMMA)(y) / $(GAMMA)(x+y).
 *
 * This implementation restricts x and y to the set of real numbers.
 *
 * Params:
 *   x = the first argument of B
 *   y = the second argument of B
 *
 * Returns:
 *   It returns B(x,y) if it can be computed, otherwise $(NAN).
 *
 * $(TABLE_SV
 *   $(TR $(TH x)                                   $(TH y)                $(TH beta(x, y))   )
 *   $(TR $(TD $(NAN))                              $(TD y)                $(TD $(NAN))       )
 *   $(TR $(TD -$(INFIN))                           $(TD y)                $(TD $(NAN))       )
 *   $(TR $(TD integer $(LT) 0)                     $(TD y)                $(TD $(NAN))       )
 *   $(TR $(TD noninteger and x+y even $(LE) 0)     $(TD noninteger)       $(TD -0)           )
 *   $(TR $(TD noninteger and x+y odd $(LE) 0)      $(TD noninteger)       $(TD +0)           )
 *   $(TR $(TD +0)                                  $(TD positive finite)  $(TD +$(INFIN))    )
 *   $(TR $(TD +0)                                  $(TD +$(INFIN))        $(TD $(NAN))       )
 *   $(TR $(TD $(GT) 0)                             $(TD +$(INFIN))        $(TD +0)           )
 *   $(TR $(TD -0)                                  $(TD +0)               $(TD $(NAN))       )
 *   $(TR $(TD -0)                                  $(TD $(GT) 0)          $(TD -$(INFIN))    )
 *   $(TR $(TD noninteger $(LT) 0, $(CEIL x) odd)   $(TD +$(INFIN))        $(TD -$(INFIN))    )
 *   $(TR $(TD noninteger $(LT) 0, $(CEIL x) even)  $(TD +$(INFIN))        $(TD +$(INFIN))    )
 *   $(TR $(TD noninteger $(LT) 0)                  $(TD $(PLUSMN)0)       $(TD $(PLUSMNINF)) )
 * )
 *
 * Since B(x,y) = B(y,x), if the table states that beta(x, y) is a special value, then beta(y, x) is
 * one as well.
 */
pragma(inline, true) real beta(real x, real y)
{
    return std.internal.math.gammafunction.beta(x, y);
}

///
@safe unittest
{
    assert(beta(1, 2) == 0.5);
}

/** Digamma function, $(PSI)(x)
 *
 * $(PSI)(x), is the logarithmic derivative of the gamma function, $(GAMMA)(x).
 *
 * $(PSI)(x) = $(SUP d)$(SUB /, dx) ln|$(GAMMA)(x)|  (the derivative of `logGamma(x)`)
 *
 * Params:
 *   x = the domain value
 *
 * Returns:
 *   It returns $(PSI)(x).
 *
 * $(TABLE_SV
 *   $(SVH x,             digamma(x)   )
 *   $(SV  integer < 0,   $(NAN)       )
 *   $(SV  $(PLUSMN)0.0,  $(MNPLUSINF) )
 *   $(SV  +$(INFIN),     +$(INFIN)    )
 *   $(SV  -$(INFIN),     $(NAN)       )
 *   $(SV  $(NAN),        $(NAN)       )
 * )
 *
 * See_Also: $(LREF logmdigamma), $(LREF logmdigammaInverse).
 */
real digamma(real x)
{
    return std.internal.math.gammafunction.digamma(x);
}

///
@safe unittest
{
    const euler = 0.57721_56649_01532_86060_65121L;

    assert(isClose(digamma(1), -euler));
    assert(digamma(+0.) == -real.infinity);
    assert(digamma(-0.) == +real.infinity);
    assert(digamma(+real.infinity) == +real.infinity);
    assert(isNaN(digamma(-1)));
    assert(isNaN(digamma(-real.infinity)));
}

/** Log Minus Digamma function
 *
 *  logmdigamma(x) = log(x) - digamma(x)
 *
 *  See_Also: $(LREF digamma), $(LREF logmdigammaInverse).
 */
real logmdigamma(real x)
{
    return std.internal.math.gammafunction.logmdigamma(x);
}

/** Inverse of the Log Minus Digamma function
 *
 *  Given y, the function finds x such log(x) - digamma(x) = y.
 *
 *  See_Also: $(LREF logmdigamma), $(LREF digamma).
 */
real logmdigammaInverse(real x)
{
    return std.internal.math.gammafunction.logmdigammaInverse(x);
}

/** Incomplete beta integral
 *
 * Returns regularized incomplete beta integral of the arguments, evaluated
 * from zero to x. The regularized incomplete beta function is defined as
 *
 * betaIncomplete(a, b, x) = $(GAMMA)(a + b) / ( $(GAMMA)(a) $(GAMMA)(b) ) *
 * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t), b-1) dt
 *
 * and is the same as the cumulative distribution function of the Beta
 * distribution.
 *
 * The domain of definition is 0 <= x <= 1.  In this
 * implementation a and b are restricted to positive values.
 * The integral from x to 1 may be obtained by the symmetry
 * relation
 *
 *    betaIncompleteCompl(a, b, x )  =  betaIncomplete( b, a, 1-x )
 *
 * The integral is evaluated by a continued fraction expansion
 * or, when b * x is small, by a power series.
 */
real betaIncomplete(real a, real b, real x )
{
    return std.internal.math.gammafunction.betaIncomplete(a, b, x);
}

/** Inverse of incomplete beta integral
 *
 * Given y, the function finds x such that
 *
 *  betaIncomplete(a, b, x) == y
 *
 *  Newton iterations or interval halving is used.
 */
real betaIncompleteInverse(real a, real b, real y )
{
    return std.internal.math.gammafunction.betaIncompleteInv(a, b, y);
}

/** Incomplete gamma integral and its complement
 *
 * These functions are defined by
 *
 *   gammaIncomplete = ( $(INTEGRATE 0, x) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
 *
 *  gammaIncompleteCompl(a,x)   =   1 - gammaIncomplete(a,x)
 * = ($(INTEGRATE x, $(INFIN)) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a)
 *
 * In this implementation both arguments must be positive.
 * The integral is evaluated by either a power series or
 * continued fraction expansion, depending on the relative
 * values of a and x.
 */
real gammaIncomplete(real a, real x )
in
{
   assert(x >= 0);
   assert(a > 0);
}
do
{
    return std.internal.math.gammafunction.gammaIncomplete(a, x);
}

/** ditto */
real gammaIncompleteCompl(real a, real x )
in
{
   assert(x >= 0);
   assert(a > 0);
}
do
{
    return std.internal.math.gammafunction.gammaIncompleteCompl(a, x);
}

/** Inverse of complemented incomplete gamma integral
 *
 * Given a and p, the function finds x such that
 *
 *  gammaIncompleteCompl( a, x ) = p.
 */
real gammaIncompleteComplInverse(real a, real p)
in
{
  assert(p >= 0 && p <= 1);
  assert(a > 0);
}
do
{
    return std.internal.math.gammafunction.gammaIncompleteComplInv(a, p);
}


/* ***********************************************
 *     ERROR FUNCTIONS & NORMAL DISTRIBUTION     *
 * ***********************************************/

 /** Error function
 *
 * The integral is
 *
 *  erf(x) =  2/ $(SQRT)($(PI))
 *     $(INTEGRATE 0, x) exp( - $(POWER t, 2)) dt
 *
 * The magnitude of x is limited to about 106.56 for IEEE 80-bit
 * arithmetic; 1 or -1 is returned outside this range.
 */
real erf(real x)
{
    return std.internal.math.errorfunction.erf(x);
}

/** Complementary error function
 *
 * erfc(x) = 1 - erf(x)
 *         = 2/ $(SQRT)($(PI))
 *     $(INTEGRATE x, $(INFIN)) exp( - $(POWER t, 2)) dt
 *
 * This function has high relative accuracy for
 * values of x far from zero. (For values near zero, use erf(x)).
 */
real erfc(real x)
{
    return std.internal.math.errorfunction.erfc(x);
}


/** Standard normal distribution function.
 *
 * The normal (or Gaussian, or bell-shaped) distribution is
 * defined as:
 *
 * normalDist(x) = 1/$(SQRT)(2$(PI)) $(INTEGRATE -$(INFIN), x) exp( - $(POWER t, 2)/2) dt
 *   = 0.5 + 0.5 * erf(x/sqrt(2))
 *   = 0.5 * erfc(- x/sqrt(2))
 *
 * To maintain accuracy at values of x near 1.0, use
 *      normalDistribution(x) = 1.0 - normalDistribution(-x).
 *
 * References:
 * $(LINK http://www.netlib.org/cephes/ldoubdoc.html),
 * G. Marsaglia, "Evaluating the Normal Distribution",
 * Journal of Statistical Software <b>11</b>, (July 2004).
 */
real normalDistribution(real x)
{
    return std.internal.math.errorfunction.normalDistributionImpl(x);
}

/** Inverse of Standard normal distribution function
 *
 * Returns the argument, x, for which the area under the
 * Normal probability density function (integrated from
 * minus infinity to x) is equal to p.
 *
 * Note: This function is only implemented to 80 bit precision.
 */
real normalDistributionInverse(real p)
in
{
  assert(p >= 0.0L && p <= 1.0L, "Domain error");
}
do
{
    return std.internal.math.errorfunction.normalDistributionInvImpl(p);
}
