use prospicio_math::special::{norm_cdf, norm_pdf, norm_quantile};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Link {
Identity,
Log,
Logit,
Probit,
Cloglog,
Inverse,
InverseSquared,
Power(f64),
}
impl Link {
pub fn link(&self, mu: f64) -> f64 {
match *self {
Self::Identity => mu,
Self::Log => mu.ln(),
Self::Logit => (mu / (1.0 - mu)).ln(),
Self::Probit => norm_quantile(mu),
Self::Cloglog => (-(-mu).ln_1p()).ln(),
Self::Inverse => 1.0 / mu,
Self::InverseSquared => 1.0 / (mu * mu),
Self::Power(0.0) => mu.ln(),
Self::Power(l) => mu.powf(l),
}
}
pub fn inverse(&self, eta: f64) -> f64 {
match *self {
Self::Identity => eta,
Self::Log => eta.exp(),
Self::Logit => 1.0 / (1.0 + (-eta).exp()),
Self::Probit => norm_cdf(eta),
Self::Cloglog => -(-eta.exp()).exp_m1(),
Self::Inverse => 1.0 / eta,
Self::InverseSquared => 1.0 / eta.sqrt(),
Self::Power(0.0) => eta.exp(),
Self::Power(l) => eta.powf(1.0 / l),
}
}
pub fn mu_eta(&self, eta: f64) -> f64 {
match *self {
Self::Identity => 1.0,
Self::Log => eta.exp(),
Self::Logit => {
let e = (-eta.abs()).exp();
e / ((1.0 + e) * (1.0 + e))
}
Self::Probit => norm_pdf(eta),
Self::Cloglog => (eta - eta.exp()).exp(),
Self::Inverse => -1.0 / (eta * eta),
Self::InverseSquared => -0.5 * eta.powf(-1.5),
Self::Power(0.0) => eta.exp(),
Self::Power(l) => eta.powf(1.0 / l - 1.0) / l,
}
}
pub fn mu_eta2(&self, eta: f64) -> f64 {
match *self {
Self::Identity => 0.0,
Self::Log | Self::Power(0.0) => eta.exp(),
Self::Logit => {
let mu = self.inverse(eta);
self.mu_eta(eta) * (1.0 - 2.0 * mu)
}
Self::Probit => -eta * norm_pdf(eta),
Self::Cloglog => self.mu_eta(eta) * (1.0 - eta.exp()),
Self::Inverse => 2.0 / (eta * eta * eta),
Self::InverseSquared => 0.75 * eta.powf(-2.5),
Self::Power(l) => (1.0 / l - 1.0) / l * eta.powf(1.0 / l - 2.0),
}
}
pub fn valid_eta(&self, eta: f64) -> bool {
eta.is_finite()
&& match *self {
Self::Inverse | Self::InverseSquared => eta > 0.0,
Self::Power(l) if l != 0.0 => eta > 0.0,
_ => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inverses_and_derivatives_agree() {
let links = [
(Link::Identity, 2.5),
(Link::Log, 2.5),
(Link::Logit, 0.3),
(Link::Probit, 0.3),
(Link::Cloglog, 0.3),
(Link::Inverse, 2.5),
(Link::InverseSquared, 2.5),
(Link::Power(0.5), 2.5),
(Link::Power(0.0), 2.5),
];
for (g, mu) in links {
let eta = g.link(mu);
assert!((g.inverse(eta) - mu).abs() < 1e-13, "{g:?}");
let h = 1e-6;
let numeric = (g.inverse(eta + h) - g.inverse(eta - h)) / (2.0 * h);
assert!(
(g.mu_eta(eta) - numeric).abs() < 1e-7 * numeric.abs().max(1.0),
"{g:?}"
);
let numeric2 = (g.mu_eta(eta + h) - g.mu_eta(eta - h)) / (2.0 * h);
assert!(
(g.mu_eta2(eta) - numeric2).abs() < 1e-7 * numeric2.abs().max(1.0),
"{g:?}"
);
}
}
}