/***************************************************************************
* GMath.cpp - Mathematical functions *
* ----------------------------------------------------------------------- *
* copyright (C) 2012-2015 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
***************************************************************************/
/**
* @file GMath.cpp
* @brief Mathematical function implementations
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include "GMath.hpp"
#include "GTools.hpp"
/* __ Method name definitions ____________________________________________ */
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
#define G_USE_ASIN_FOR_ACOS //!< Use asin for acos computations
/* __ Debug definitions __________________________________________________ */
/*==========================================================================
= =
= Functions =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Computes acos by avoiding NaN due to rounding errors
*
* @param[in] arg Argument.
* @return Arc cosine of argument.
*
* Returns the arc cosine by restricting the argument to [-1,1].
*
* If the compile option G_USE_ASIN_FOR_ACOS is defined, the function will
* compute
*
* \f[
* acos(x) = \frac{\pi}{2} - asin(x)
* \f]
*
* which happens to be faster on most systems.
***************************************************************************/
double gammalib::acos(const double& arg)
{
// Allocate result
double arccos;
// Compute acos
if (arg >= 1) {
arccos = 0.0;
}
else if (arg <= -1.0) {
arccos = gammalib::pi;
}
else {
#if defined(G_USE_ASIN_FOR_ACOS)
arccos = gammalib::pihalf - std::asin(arg);
#else
arccos = std::acos(arg);
#endif
}
// Return result
return arccos;
}
/***********************************************************************//**
* @brief Compute cosine of angle in degrees
*
* @param[in] angle Angle in degrees
*
* This code has been adapted from the WCSLIB function wcstrig.c::cosd().
***************************************************************************/
double gammalib::cosd(const double& angle)
{
// Check for rounding errors
if (fmod(angle, 90.0) == 0.0) {
int i = std::abs((int)std::floor(angle/90.0 + 0.5)) % 4;
switch (i) {
case 0:
return 1.0;
case 1:
return 0.0;
case 2:
return -1.0;
case 3:
return 0.0;
}
}
// Return cosine
return std::cos(angle * gammalib::deg2rad);
}
/***********************************************************************//**
* @brief Compute sine of angle in degrees
*
* @param[in] angle Angle in degrees
*
* This code has been adapted from the WCSLIB function wcstrig.c::sind().
***************************************************************************/
double gammalib::sind(const double& angle)
{
// Check for rounding errors
if (fmod(angle, 90.0) == 0.0) {
int i = std::abs((int)std::floor(angle/90.0 - 0.5))%4;
switch (i) {
case 0:
return 1.0;
case 1:
return 0.0;
case 2:
return -1.0;
case 3:
return 0.0;
}
}
// Return sine
return std::sin(angle * gammalib::deg2rad);
}
/***********************************************************************//**
* @brief Compute tangens of angle in degrees
*
* @param[in] angle Angle in degrees
*
* This code has been adapted from the WCSLIB function wcstrig.c::tand().
***************************************************************************/
double gammalib::tand(const double& angle)
{
// Check for rounding errors
double resid = fmod(angle, 360.0);
if (resid == 0.0 || std::abs(resid) == 180.0) {
return 0.0;
}
else if (resid == 45.0 || resid == 225.0) {
return 1.0;
}
else if (resid == -135.0 || resid == -315.0) {
return -1.0;
}
// Return tangens
return std::tan(angle * gammalib::deg2rad);
}
/***********************************************************************//**
* @brief Compute arc cosine in degrees
*
* @param[in] value Value
*
* This code has been adapted from the WCSLIB function wcstrig.c::acosd().
***************************************************************************/
double gammalib::acosd(const double& value)
{
// Domain tolerance
const double wcstrig_tol = 1.0e-10;
// Check for rounding errors
if (value >= 1.0) {
if (value-1.0 < wcstrig_tol) {
return 0.0;
}
}
else if (value == 0.0) {
return 90.0;
}
else if (value <= -1.0) {
if (value+1.0 > -wcstrig_tol) {
return 180.0;
}
}
// Return arc cosine
return std::acos(value) * gammalib::rad2deg;
}
/***********************************************************************//**
* @brief Compute arc sine in degrees
*
* @param[in] value Value
*
* This code has been adapted from the WCSLIB function wcstrig.c::asind().
***************************************************************************/
double gammalib::asind(const double& value)
{
// Domain tolerance
const double wcstrig_tol = 1.0e-10;
// Check for rounding errors
if (value <= -1.0) {
if (value+1.0 > -wcstrig_tol) {
return -90.0;
}
}
else if (value == 0.0) {
return 0.0;
}
else if (value >= 1.0) {
if (value-1.0 < wcstrig_tol) {
return 90.0;
}
}
// Return arc sine
return std::asin(value) * gammalib::rad2deg;
}
/***********************************************************************//**
* @brief Compute arc tangens in degrees
*
* @param[in] value Value
*
* This code has been adapted from the WCSLIB function wcstrig.c::atand().
***************************************************************************/
double gammalib::atand(const double& value)
{
// Check for rounding errors
if (value == -1.0) {
return -45.0;
}
else if (value == 0.0) {
return 0.0;
}
else if (value == 1.0) {
return 45.0;
}
// Return arc sine
return std::atan(value) * gammalib::rad2deg;
}
/***********************************************************************//**
* @brief Compute arc tangens in degrees
*
* @param[in] y Nominator
* @param[in] x Denominator
*
* This code has been adapted from the WCSLIB function wcstrig.c::atan2d().
***************************************************************************/
double gammalib::atan2d(const double& y, const double& x)
{
// Check for rounding errors
if (y == 0.0) {
if (x >= 0.0) {
return 0.0;
}
else if (x < 0.0) {
return 180.0;
}
}
else if (x == 0.0) {
if (y > 0.0) {
return 90.0;
}
else if (y < 0.0) {
return -90.0;
}
}
// Return arc sine
return std::atan2(y,x) * gammalib::rad2deg;
}
/***********************************************************************//**
* @brief Compute sine and cosine of angle in degrees
*
* @param[in] angle Angle [degrees].
* @param[out] s Sine of angle.
* @param[out] c Cosine of angle.
*
* This code has been adapted from the WCSLIB function wcstrig.c::sincosd().
***************************************************************************/
void gammalib::sincosd(const double& angle, double *s, double *c)
{
// Check for rounding errors
if (fmod(angle, 90.0) == 0.0) {
int i = std::abs((int)std::floor(angle/90.0 + 0.5)) % 4;
switch (i) {
case 0:
*s = 0.0;
*c = 1.0;
return;
case 1:
*s = (angle > 0.0) ? 1.0 : -1.0;
*c = 0.0;
return;
case 2:
*s = 0.0;
*c = -1.0;
return;
case 3:
*s = (angle > 0.0) ? -1.0 : 1.0;
*c = 0.0;
return;
}
}
// Compute sine and cosine
*s = std::sin(angle * gammalib::deg2rad);
*c = std::cos(angle * gammalib::deg2rad);
// Return
return;
}
/***********************************************************************//**
* @brief Computes logarithm of gamma function
*
* @param[in] arg Argument.
* @return Logarithm of gamma function.
***************************************************************************/
double gammalib::gammln(const double& arg) {
// Define static constants
static const double cof[6] = { 76.18009172947146,
-86.50532032941677,
24.01409824083091,
-1.231739572450155,
0.1208650973866179e-2,
-0.5395239384953e-5};
// Evaluate logarithm of gamma function
double a = arg;
double b = arg;
double c = a + 5.5;
c -= (a + 0.5) * std::log(c);
double d = 1.000000000190015;
for (int i = 0; i < 6; ++i) {
d += cof[i]/++b;
}
double result = std::log(2.5066282746310005 * d/a) - c;
// Return result
return result;
}
/***********************************************************************//**
* @brief Computes complementary error function
*
* @param[in] arg Argument.
* @return Complementary error function.
*
* Reference: http://en.wikipedia.org/wiki/Complementary_error_function
***************************************************************************/
double gammalib::erfcc(const double& arg)
{
// Copied from Gaussian.cxx in the Fermi ScienceTools software
double t, z, ans;
z = std::abs(arg);
t = 1.0/(1.0+0.5*z);
ans = t*std::exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+
t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+
t*(-0.82215223+t*0.17087277)))))))));
double result = arg >= 0.0 ? ans : 2.0-ans;
// Return result
return result;
}
/***********************************************************************//**
* @brief Computes inverse error function
*
* @param[in] arg Argument.
* @return Inverse error function.
***************************************************************************/
double gammalib::erfinv(const double& arg)
{
// Define constants
static const double a[4] = { 0.886226899, -1.645349621, 0.914624893,
-0.140543331};
static const double b[4] = {-2.118377725, 1.442710462, -0.329097515,
0.012229801};
static const double c[4] = {-1.970840454, -1.624906493, 3.429567803,
1.641345311};
static const double d[2] = { 3.543889200, 1.637067800};
static const double e = 2.0/std::sqrt(gammalib::pi);
// Allocate result
double result;
// Return NAN if out of range
if (std::abs(arg > 1.0)) {
result = std::atof("NAN");
}
// Return maximum double if at range limit
else if (std::abs(arg == 1.0)) {
result = copysign(1.0, arg) * DBL_MAX;
}
// Otherwise compute inverse of error function
else {
// Compute a rational approximation of the inverse error function
if (std::abs(arg) <= 0.7) {
double z = arg * arg;
double num = (((a[3]*z + a[2])*z + a[1])*z + a[0]);
double dem = ((((b[3]*z + b[2])*z + b[1])*z +b[0])*z + 1.0);
result = arg * num/dem;
}
else {
double z = std::sqrt(-std::log((1.0-std::abs(arg))/2.0));
double num = ((c[3]*z + c[2])*z + c[1])*z + c[0];
double dem = (d[1]*z + d[0])*z + 1.0;
result = (copysign(1.0, arg)) * num/dem;
}
// Now do two steps of Newton-Raphson correction
result -= (erf(result) - arg) / (e * std::exp(-result*result));
result -= (erf(result) - arg) / (e * std::exp(-result*result));
}
// Return result
return result;
}
/***********************************************************************//**
* @brief Returns the remainder of the division
*
* @param[in] v1 Nominator.
* @param[in] v2 Denominator.
* @return Remainder of division
*
* Returns the remainder of the division @a v1/v2. The result is non-negative.
* @a v1 can be positive or negative; @a v2 must be positive.
***************************************************************************/
double gammalib::modulo(const double& v1, const double& v2)
{
// Declare result
double result;
//
if (v1 >= 0.0) {
result = (v1 < v2) ? v1 : std::fmod(v1,v2);
}
else {
double tmp = std::fmod(v1,v2) + v2;
result = (tmp == v2) ? 0.0 : tmp;
}
// Return result
return result;
}