Skip to content

Commit 3492433

Browse files
authored
Add derivative support for std::beta and introduce digamma function utility (#1733)
* Add analytical derivative support for std::beta * feat: add trigamma function
1 parent 830cd9c commit 3492433

3 files changed

Lines changed: 148 additions & 1 deletion

File tree

include/clad/Differentiator/BuiltinDerivatives.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,112 @@ CUDA_HOST_DEVICE void hypot_pullback(T x, T y, U d_z, T* d_x, T* d_y) {
10621062
*d_y += (y / h) * d_z;
10631063
}
10641064

1065+
// 7. Special Functions
1066+
#if __cplusplus >= 201703L
1067+
template <typename T> CUDA_HOST_DEVICE inline T clad_beta_primal(T x, T y) {
1068+
#if defined(__cpp_lib_math_special_functions)
1069+
return ::std::beta(x, y);
1070+
#else
1071+
return ::std::tgamma(x) * ::std::tgamma(y) / ::std::tgamma(x + y);
1072+
#endif
1073+
}
1074+
// Computes the digamma function psi(x) using a standard asymptotic expansion
1075+
// NOTE: This is a numerical approximation
1076+
// Reference: Wolfram MathWorld, Digamma Function
1077+
// https://mathworld.wolfram.com/DigammaFunction.html
1078+
1079+
template <typename T> CUDA_HOST_DEVICE inline T clad_digamma(T x) {
1080+
if (x <= 0.0) {
1081+
if (x == ::std::floor(x))
1082+
return (T)NAN;
1083+
return clad_digamma(1.0 - x) -
1084+
::std::acos((T)-1.0) / ::std::tan(::std::acos((T)-1.0) * x);
1085+
}
1086+
T result = 0.0;
1087+
while (x < 8.0) {
1088+
result -= 1.0 / x;
1089+
x += 1.0;
1090+
}
1091+
T inv_x = 1.0 / x;
1092+
T inv_x2 = inv_x * inv_x;
1093+
result +=
1094+
::std::log(x) - 0.5 * inv_x -
1095+
inv_x2 * (1.0 / 12.0 -
1096+
inv_x2 * (1.0 / 120.0 -
1097+
inv_x2 * (1.0 / 252.0 - inv_x2 * (1.0 / 240.0))));
1098+
return result;
1099+
}
1100+
1101+
// NOTE: Like digamma this uses a truncated asymptotic expansion.
1102+
// Used as a custom derivative for digamma
1103+
// Reference: Wolfram MathWorld, Trigamma Function
1104+
// https://mathworld.wolfram.com/TrigammaFunction.html
1105+
1106+
template <typename T> CUDA_HOST_DEVICE inline T clad_trigamma(T x) {
1107+
if (x <= 0.0) {
1108+
if (x == ::std::floor(x))
1109+
return (T)NAN;
1110+
T pi = ::std::acos((T)-1.0);
1111+
T csc = 1.0 / ::std::sin(pi * x);
1112+
return -clad_trigamma(1.0 - x) + (pi * pi * csc * csc);
1113+
}
1114+
1115+
T result = 0.0;
1116+
while (x < 8.0) {
1117+
result += 1.0 / (x * x);
1118+
x += 1.0;
1119+
}
1120+
1121+
T inv_x = 1.0 / x;
1122+
T inv_x2 = inv_x * inv_x;
1123+
1124+
result += inv_x + 0.5 * inv_x2 +
1125+
inv_x2 * inv_x *
1126+
(1.0 / 6.0 -
1127+
inv_x2 * (1.0 / 30.0 -
1128+
inv_x2 * (1.0 / 42.0 - inv_x2 * (1.0 / 30.0))));
1129+
return result;
1130+
}
1131+
1132+
template <typename T, typename dT>
1133+
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> digamma_pushforward(T x, dT d_x) {
1134+
T psi = clad_digamma(x);
1135+
dT pushforward = 0;
1136+
if (d_x)
1137+
pushforward += clad_trigamma(x) * d_x;
1138+
return {psi, pushforward};
1139+
}
1140+
1141+
template <typename T, typename U>
1142+
CUDA_HOST_DEVICE void digamma_pullback(T x, U d_z, T* d_x) {
1143+
if (d_x)
1144+
*d_x += clad_trigamma(x) * d_z;
1145+
}
1146+
1147+
template <typename T, typename dT>
1148+
CUDA_HOST_DEVICE ValueAndPushforward<T, dT> beta_pushforward(T x, T y, dT d_x,
1149+
dT d_y) {
1150+
T b = clad_beta_primal(x, y);
1151+
T psi_xy = clad_digamma(x + y);
1152+
dT pushforward = 0;
1153+
if (d_x)
1154+
pushforward += b * (clad_digamma(x) - psi_xy) * d_x;
1155+
if (d_y)
1156+
pushforward += b * (clad_digamma(y) - psi_xy) * d_y;
1157+
return {b, pushforward};
1158+
}
1159+
1160+
template <typename T, typename U>
1161+
CUDA_HOST_DEVICE void beta_pullback(T x, T y, U d_z, T* d_x, T* d_y) {
1162+
T b = clad_beta_primal(x, y);
1163+
T psi_xy = clad_digamma(x + y);
1164+
if (d_x)
1165+
*d_x += b * (clad_digamma(x) - psi_xy) * d_z;
1166+
if (d_y)
1167+
*d_y += b * (clad_digamma(y) - psi_xy) * d_z;
1168+
}
1169+
#endif
1170+
10651171
} // namespace std
10661172

10671173
CUDA_HOST_DEVICE inline ValueAndPushforward<float, float>
@@ -1327,6 +1433,12 @@ using std::pow_pullback;
13271433
using std::pow_pushforward;
13281434
using std::sqrt_pushforward;
13291435

1436+
// 7. Special Functions
1437+
#if __cplusplus >= 201703L
1438+
using std::beta_pullback;
1439+
using std::beta_pushforward;
1440+
#endif
1441+
13301442
namespace class_functions {
13311443
template <typename T, typename U>
13321444
void constructor_pullback(ValueAndPushforward<T, U> rhs,

test/Features/stl-cmath.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
//----------------------- Mathematical special functions -----------------------
9999
// D assoc_laguerre / f / l (C++17) associated Laguerre polynomials
100100
// D assoc_legendre/ f / l (C++17) associated Legendre polynomials
101-
// D beta/ betaf/ betal (C++17) beta function
101+
// DS beta/ betaf/ betal (C++17) beta function
102102
// D comp_ellint_1/ f / l (C++17) complete elliptic integral (1st kind)
103103
// D comp_ellint_2/ f / l (C++17) complete elliptic integral (2nd kind)
104104
// D comp_ellint_3/ f / l (C++17) complete elliptic integral (3rd kind)
@@ -123,6 +123,14 @@
123123
#include <cmath>
124124
#include <iostream>
125125
#include <iomanip>
126+
#if !defined(__cpp_lib_math_special_functions)
127+
namespace std {
128+
template <typename T>
129+
inline T beta(T x, T y) {
130+
return std::tgamma(x) * std::tgamma(y) / std::tgamma(x + y);
131+
}
132+
}
133+
#endif
126134

127135
template <typename T>
128136
T get_tolerance() {
@@ -295,6 +303,10 @@ DEFINE_FUNCTIONS(atanh) // x in [-1,1]
295303
//
296304
DEFINE_FUNCTIONS(erf) // x in (-inf,+inf)
297305

306+
template<typename T> T f_beta(T x){ return std::beta(x,(T)2.0); } // x in (0, +inf)
307+
inline float f_betaf(float x){ return std::beta(x, 2.0f); }
308+
inline long double f_betal(long double x){ return std::beta(x, 2.0L); }
309+
298310
int main() {
299311
// Absolute value
300312
CHECK(abs);
@@ -352,6 +364,7 @@ int main() {
352364

353365
// Error / Gamma functions
354366
CHECK_ALL(erf);
367+
CHECK_ALL_RANGE(beta, {0.1, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0});
355368

356369
return 0;
357370
}

test/FirstDerivative/BuiltinDerivatives.C

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77
#include "clad/Differentiator/Differentiator.h"
88
#include "../TestUtils.h"
9+
#include <cmath>
10+
11+
#if !defined(__cpp_lib_math_special_functions)
12+
namespace std {
13+
// Mock std::beta on Apple platforms so Clad's AST has a target to differentiate
14+
inline double beta(double x, double y) {
15+
return std::tgamma(x) * std::tgamma(y) / std::tgamma(x + y);
16+
}
17+
}
18+
#endif
19+
920
extern "C" int printf(const char* fmt, ...);
1021

1122

@@ -515,6 +526,14 @@ double f_custom_min(double x, double y) { return std::min(x, y, std::greater<dou
515526
// CHECK-NEXT: return _t0.pushforward;
516527
// CHECK-NEXT: }
517528

529+
double f_beta(double x, double y) { return std::beta(x, y); }
530+
// CHECK: double f_beta_darg0(double x, double y) {
531+
// CHECK-NEXT: double _d_x = 1;
532+
// CHECK-NEXT: double _d_y = 0;
533+
// CHECK-NEXT: {{.*}}ValueAndPushforward<double, double> _t0 = {{.*}}beta_pushforward(x, y, _d_x, _d_y);
534+
// CHECK-NEXT: return _t0.pushforward;
535+
// CHECK-NEXT: }
536+
518537
int main () { //expected-no-diagnostics
519538
float f_result[2];
520539
double d_result[2];
@@ -695,5 +714,8 @@ int main () { //expected-no-diagnostics
695714
auto d_custom_min = clad::differentiate(f_custom_min, 0);
696715
printf("Result is = %.6f\n", d_custom_min.execute(2, 3)); // CHECK-EXEC: Result is = 0.000000
697716

717+
auto d_beta = clad::differentiate(f_beta, 0);
718+
printf("Result is = %.6f\n", d_beta.execute(2.0, 3.0)); // CHECK-EXEC: Result is = -0.090278
719+
698720
return 0;
699721
}

0 commit comments

Comments
 (0)