use num_traits::Float;
#[inline]
pub fn hypot_partials<T: Float>(a: T, b: T, r: T) -> (T, T) {
if r == T::zero() {
(T::zero(), T::zero())
} else {
(a / r, b / r)
}
}
#[inline]
pub fn atan2_partials<T: Float>(a: T, b: T) -> (T, T) {
let h = a.hypot(b);
if h == T::zero() {
(T::zero(), T::zero())
} else {
(b / h / h, -a / h / h)
}
}
#[inline]
pub fn atan_deriv<T: Float>(a: T) -> T {
let one = T::one();
if a.abs() > T::from(1e8).unwrap() {
let inv = one / a;
inv * inv / (one + inv * inv)
} else {
one / (one + a * a)
}
}
#[inline]
pub fn asinh_deriv<T: Float>(a: T) -> T {
let one = T::one();
if a.abs() > T::from(1e8).unwrap() {
let inv = one / a;
inv.abs() / (one + inv * inv).sqrt()
} else {
one / (a * a + one).sqrt()
}
}
#[inline]
pub fn acosh_deriv<T: Float>(a: T) -> T {
let one = T::one();
if a < one {
return T::nan();
}
if a > T::from(1e8).unwrap() {
let inv = one / a;
inv.abs() / (one - inv * inv).sqrt()
} else {
one / ((a - one) * (a + one)).sqrt()
}
}
#[inline]
pub fn ln_deriv<T: Float>(a: T) -> T {
if a >= T::zero() {
T::one() / a
} else {
T::nan()
}
}
#[inline]
pub fn log2_deriv<T: Float>(a: T) -> T {
if a >= T::zero() {
T::one() / (a * T::from(2.0).unwrap().ln())
} else {
T::nan()
}
}
#[inline]
pub fn log10_deriv<T: Float>(a: T) -> T {
if a >= T::zero() {
T::one() / (a * T::from(10.0).unwrap().ln())
} else {
T::nan()
}
}
#[inline]
pub fn ln_1p_deriv<T: Float>(a: T) -> T {
if a >= -T::one() {
T::one() / (T::one() + a)
} else {
T::nan()
}
}
#[inline]
pub fn atanh_deriv<T: Float>(a: T) -> T {
if a >= -T::one() && a <= T::one() {
T::one() / ((T::one() - a) * (T::one() + a))
} else {
T::nan()
}
}
#[inline]
pub fn abs_deriv<T: Float>(a: T) -> T {
if a == T::zero() {
T::zero()
} else {
a.signum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn domain_restricted_derivs_guard_outside_and_keep_boundary() {
assert!(ln_deriv(-2.0_f64).is_nan());
assert!(log2_deriv(-2.0_f64).is_nan());
assert!(log10_deriv(-2.0_f64).is_nan());
assert!(ln_1p_deriv(-2.0_f64).is_nan());
assert!(atanh_deriv(1.5_f64).is_nan());
assert!(atanh_deriv(-1.5_f64).is_nan());
assert!(ln_deriv(0.0_f64).is_infinite() && ln_deriv(0.0_f64) > 0.0);
assert!(log2_deriv(0.0_f64).is_infinite() && log2_deriv(0.0_f64) > 0.0);
assert!(log10_deriv(0.0_f64).is_infinite() && log10_deriv(0.0_f64) > 0.0);
assert!(ln_1p_deriv(-1.0_f64).is_infinite() && ln_1p_deriv(-1.0_f64) > 0.0);
assert!(atanh_deriv(1.0_f64).is_infinite() && atanh_deriv(1.0_f64) > 0.0);
assert!(atanh_deriv(-1.0_f64).is_infinite() && atanh_deriv(-1.0_f64) > 0.0);
assert!((ln_deriv(2.0_f64) - 0.5).abs() < 1e-15);
assert!((log2_deriv(2.0_f64) - 1.0 / (2.0 * 2.0_f64.ln())).abs() < 1e-15);
assert!((log10_deriv(2.0_f64) - 1.0 / (2.0 * 10.0_f64.ln())).abs() < 1e-15);
assert!((ln_1p_deriv(3.0_f64) - 0.25).abs() < 1e-15);
assert!((atanh_deriv(0.5_f64) - 1.0 / (0.75)).abs() < 1e-15);
assert!(acosh_deriv(-1.5_f64).is_nan());
assert!(acosh_deriv(-2.0_f64).is_nan());
assert!(acosh_deriv(0.5_f64).is_nan());
assert!(acosh_deriv(-1.0_f64).is_nan());
assert!(acosh_deriv(-1e9_f64).is_nan()); assert!(acosh_deriv(1.0_f64).is_infinite() && acosh_deriv(1.0_f64) > 0.0);
assert!((acosh_deriv(2.0_f64) - 1.0 / 3.0_f64.sqrt()).abs() < 1e-15);
}
#[test]
fn abs_deriv_is_zero_at_the_kink_regardless_of_sign_bit() {
assert_eq!(abs_deriv(0.0_f64), 0.0);
assert_eq!(abs_deriv(-0.0_f64), 0.0);
assert_eq!(abs_deriv(2.0_f64), 1.0);
assert_eq!(abs_deriv(-3.0_f64), -1.0);
assert!(abs_deriv(f64::NAN).is_nan());
}
#[test]
fn acosh_deriv_factored_form_keeps_precision_near_one() {
fn acosh_deriv_unfactored(a: f64) -> f64 {
1.0 / (a * a - 1.0).sqrt()
}
let a = 1.0 + 1e-12_f64;
let factored = acosh_deriv::<f64>(a);
let unfactored = acosh_deriv_unfactored(a);
let rel_diff = (factored - unfactored).abs() / factored.max(unfactored);
assert!(
rel_diff > 1e-13,
"kernel must use factored (a-1)·(a+1) form: factored={factored}, unfactored={unfactored}, rel_diff={rel_diff:e} (a swap back to a*a-1 would make them equal)"
);
}
}