pub trait LnExp {
// Required methods
fn ln_1m_exp(&self) -> Self;
fn ln_1p_exp(&self) -> Self;
fn ln_exp_m1(&self) -> Self;
fn logit(&self) -> Self;
fn inv_logit(&self) -> Self;
fn ln_inv_logit(&self) -> Self;
fn logit_exp(&self) -> Self;
fn ln_1m_inv_logit(&self) -> Self;
fn logit_1m_exp(&self) -> Self;
}
Expand description
The LnExp
trait extends the functionality afforded by a libm
, providing
careful evaluation of compositions of the ln
, exp
, ln_1p
and exp_m1
functions so as to avoid underflow/overflow. Furthermore, in some cases, the
methods provided by this trait result in fewer calls than the equivalent
function composition.
Required Methods§
sourcefn ln_1m_exp(&self) -> Self
fn ln_1m_exp(&self) -> Self
Returns ln(1 - exp(x))
, computed as described in
Martin Maechler (2012), Accurately Computing log(1 − exp(− |a|)).
For x > 0, the result is undefined, hence, the return value is nan
.
Examples
use lnexp::LnExp;
let x: f64 = -1e-20;
assert!(x.ln_1m_exp().is_finite());
// compare with the naive computation
assert!((-x.exp()).ln_1p().is_infinite());
let x: f64 = -50.0;
assert_ne!(x.ln_1m_exp(), 0.0);
// the result if we blindly applied `ln(-exp_m1(x))` to the entire range
assert_eq!((-x.exp_m1()).ln(), 0.0);
// An interesting property: the inverse of `ln_1m_exp` is `ln_1m_exp`.
let x: f64 = -2.0;
assert_eq!(x.ln_1m_exp().ln_1m_exp(), x);
sourcefn ln_1p_exp(&self) -> Self
fn ln_1p_exp(&self) -> Self
Returns ln(1 + exp(x))
, computed as described in Maechler (2012),
for which a theoretical basis has been provided by cossio (2022), untitled.
The inverse of this function is ln_exp_m1
.
Examples
use lnexp::LnExp;
let x: f64 = (1023.0 * (2.0_f64).ln()) + 1.0;
assert!(x.ln_1p_exp().is_finite());
// compare with naive computation
assert_eq!(x.exp().ln_1p(), f64::INFINITY);
sourcefn ln_exp_m1(&self) -> Self
fn ln_exp_m1(&self) -> Self
Returns ln(exp(x) - 1)
, the inverse of ln_1p_exp
, computed by inverting
the case analysis described in Maechler (2012).
Examples
use lnexp::LnExp;
let x: f64 = (1023.0 * (2.0_f64).ln()) + 1.0;
assert!(x.ln_exp_m1().is_finite());
// compare with naive computation
assert_eq!(x.exp_m1().ln(), f64::INFINITY);
sourcefn inv_logit(&self) -> Self
fn inv_logit(&self) -> Self
Returns the inverse-logit mapping from a real number to the closed interval [0,1].
This is the inverse of logit
.
Examples
use lnexp::LnExp;
let x: f64 = f64::INFINITY;
assert_eq!(x.inv_logit(), 1.0);
let x: f64 = 0.0;
assert_eq!(x.inv_logit(), 0.5);
let x: f64 = f64::NEG_INFINITY;
assert_eq!(x.inv_logit(), 0.0);
// Smooth lower bound by enforcing that values less than
// `logit(5.0e-324)` (`logit(1.0e-45)` if `f32`) map to 0.0
// when the inverse-logit is applied.
let x: f64 = -745.0; // Slightly less than lower bound
assert_eq!(x.inv_logit(), 0.0);
// compare with naive computation
let e = x.exp();
assert_ne!(e / (1.0 + e), 0.0);
// Smooth upper bound by enforcing that values greater than
// `logit(1.0 - epsilon / 2.0)` map to 1.0 when the inverse-logit is applied.
let x: f64 = 36.8; // Slightly larger than upper bound
assert_eq!(x.inv_logit(), 1.0);
// compare with naive computation
let e = x.exp();
assert_ne!(e / (1.0 + e), 1.0);
sourcefn ln_inv_logit(&self) -> Self
fn ln_inv_logit(&self) -> Self
Returns the natural logarithm of the inverse-logit function, computed
more carefully than the composition of functions x.inv_logit().ln()
.
This utilizes the identity ln(inv_logit(x)) = -ln(1 + exp(-x))
to compute the result via ln_1p_exp
.
Examples
use lnexp::LnExp;
let x: f64 = -745.0;
assert!(x.ln_inv_logit().is_finite());
// compare with naive computation
assert_eq!(x.inv_logit().ln(), f64::NEG_INFINITY);
sourcefn logit_exp(&self) -> Self
fn logit_exp(&self) -> Self
Returns the logit of the exponential of self
, i.e. logit(exp(x))
,
computed more carefully and with fewer function calls than the composition.
This is the inverse of ln_inv_logit
.
Examples
use lnexp::LnExp;
let x: f64 = 50.0;
assert_eq!(x.ln_inv_logit().logit_exp(), x);
let x: f64 = 743.0;
assert!(x.ln_inv_logit().logit_exp().is_finite());
// compare with naive computation
assert_eq!(x.ln_inv_logit().exp().logit(), f64::INFINITY);
sourcefn ln_1m_inv_logit(&self) -> Self
fn ln_1m_inv_logit(&self) -> Self
Returns the natural logarithm of the 1 minus the inverse logit function,
computed more carefully and with fewer function calls than the
composition of functions (1.0 - x.inv_logit()).ln()
. This exploits negation
in the log-odds domain to compute the result via ln_1p_exp
,
Examples
use lnexp::LnExp;
let x: f64 = 50.0;
assert!(x.ln_1m_inv_logit().is_finite());
// compare with naive computation
assert_eq!((1.0 - x.inv_logit()).ln(), f64::NEG_INFINITY);
sourcefn logit_1m_exp(&self) -> Self
fn logit_1m_exp(&self) -> Self
Returns the logit of 1 minus the exponential of self
, i.e.
logit(1 - exp(x))
, computed more carefully and with fewer function calls
than the composition. This is the inverse of ln_1m_inv_logit
.
Examples
use lnexp::LnExp;
let x: f64 = 50.0;
assert_eq!(x.ln_1m_inv_logit().logit_1m_exp(), x);
let x: f64 = 743.0;
assert!(x.ln_1m_inv_logit().is_finite());
// compare with naive computation
assert_eq!((1.0 - x.ln_1m_inv_logit().exp()).logit(), f64::INFINITY);