use ndarray::{Array1, Array2};
use super::{GlmFit, NegativeBinomial};
use crate::error::{RegressionError, Result};
const THETA_MIN: f64 = 1e-3;
const THETA_MAX: f64 = 1e6;
pub fn fit_negative_binomial(x: Array2<f64>, y: Array1<f64>) -> Result<GlmFit<NegativeBinomial>> {
let neg_ll = |t: f64| -> f64 {
let theta = t.exp();
match NegativeBinomial::new(theta)
.and_then(|fam| GlmFit::new(fam, x.clone(), y.clone()))
{
Ok(fit) => -fit.log_likelihood(),
Err(_) => f64::INFINITY,
}
};
let phi = (5.0_f64.sqrt() - 1.0) / 2.0;
let mut lo = THETA_MIN.ln();
let mut hi = THETA_MAX.ln();
let mut c = hi - phi * (hi - lo);
let mut d = lo + phi * (hi - lo);
let mut fc = neg_ll(c);
let mut fd = neg_ll(d);
for _ in 0..200 {
if fc < fd {
hi = d;
d = c;
fd = fc;
c = hi - phi * (hi - lo);
fc = neg_ll(c);
} else {
lo = c;
c = d;
fc = fd;
d = lo + phi * (hi - lo);
fd = neg_ll(d);
}
if (hi - lo) < 1e-8 {
break;
}
}
let t_hat = 0.5 * (lo + hi);
let theta_hat = t_hat.exp().clamp(THETA_MIN, THETA_MAX);
let fam = NegativeBinomial::new(theta_hat).map_err(|_| RegressionError::InvalidParameter {
msg: "estimated θ out of range".into(),
})?;
GlmFit::new(fam, x, y)
}