use super::{WorkingDerivativeBuffersMut, working_deriv_slices, working_slices};
use crate::estimate::EstimationError;
use ndarray::{Array1, ArrayView1};
use rayon::iter::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator,
IntoParallelRefMutIterator, ParallelIterator,
};
pub(super) enum WorkingWeight {
PoissonIdentity,
Constant { factor: f64 },
TweediePower { p: f64, phi: f64 },
NegativeBinomial { theta: f64 },
}
pub(super) enum WorkingCurvature {
Proportional { c_ratio: f64, d_ratio: f64 },
NegativeBinomial { theta: f64 },
}
pub(super) struct LogLinkRule {
pub weight: WorkingWeight,
pub curvature: WorkingCurvature,
}
#[derive(Clone, Copy)]
struct ExactLogLinkRow {
mu: f64,
weight: f64,
c: f64,
d: f64,
}
#[derive(Clone, Copy)]
struct ExactLogLinkWorkingRow {
geometry: ExactLogLinkRow,
z: f64,
}
#[inline]
fn unrepresentable(row: usize, quantity: &'static str, eta: f64, value: f64) -> EstimationError {
EstimationError::PirlsRowGeometryUnrepresentable {
row,
quantity,
eta,
value,
}
}
#[inline]
fn exact_prior_weight(row: usize, eta: f64, prior_weight: f64) -> Result<f64, EstimationError> {
if prior_weight.is_finite() && prior_weight >= 0.0 {
Ok(prior_weight)
} else {
Err(unrepresentable(row, "prior weight", eta, prior_weight))
}
}
#[inline]
fn unit_weight(weight: &WorkingWeight, mu: f64) -> f64 {
match *weight {
WorkingWeight::PoissonIdentity => mu,
WorkingWeight::Constant { factor } => factor,
WorkingWeight::TweediePower { p, phi } => mu.powf(2.0 - p) / phi,
WorkingWeight::NegativeBinomial { theta } => {
if theta >= mu {
mu / (1.0 + mu / theta)
} else {
theta / (1.0 + theta / mu)
}
}
}
}
#[inline]
fn curvature_terms(curvature: &WorkingCurvature, mu: f64, weight: f64) -> (f64, f64) {
match *curvature {
WorkingCurvature::Proportional { c_ratio, d_ratio } => (c_ratio * weight, d_ratio * weight),
WorkingCurvature::NegativeBinomial { theta } => {
let r = if theta >= mu {
1.0 / (1.0 + mu / theta)
} else {
let theta_over_mu = theta / mu;
theta_over_mu / (1.0 + theta_over_mu)
};
let c = weight * r;
let d = c * (2.0 * r - 1.0);
(c, d)
}
}
}
#[inline]
fn exact_log_link_row(
rule: &LogLinkRule,
row: usize,
eta: f64,
prior_weight: f64,
) -> Result<ExactLogLinkRow, EstimationError> {
let mu = crate::mixture_link::log_link_solver_exp(eta)?;
let prior_weight = exact_prior_weight(row, eta, prior_weight)?;
if prior_weight == 0.0 {
return Ok(ExactLogLinkRow {
mu,
weight: 0.0,
c: 0.0,
d: 0.0,
});
}
let unit_weight = unit_weight(&rule.weight, mu);
if !(unit_weight.is_finite() && unit_weight > 0.0) {
return Err(unrepresentable(row, "unit Fisher weight", eta, unit_weight));
}
let weight = prior_weight * unit_weight;
if !(weight.is_finite() && weight > 0.0) {
return Err(unrepresentable(row, "Fisher weight", eta, weight));
}
let (c, d) = curvature_terms(&rule.curvature, mu, weight);
if !c.is_finite() {
return Err(unrepresentable(row, "dW/deta", eta, c));
}
if !d.is_finite() {
return Err(unrepresentable(row, "d2W/deta2", eta, d));
}
Ok(ExactLogLinkRow { mu, weight, c, d })
}
#[inline]
fn exact_working_response(row: usize, eta: f64, y: f64, mu: f64) -> Result<f64, EstimationError> {
let z = eta + y / mu - 1.0;
if z.is_finite() {
Ok(z)
} else {
Err(unrepresentable(row, "working response", eta, z))
}
}
fn certify_working_rows(
rule: &LogLinkRule,
y: ArrayView1<f64>,
eta: &Array1<f64>,
priorweights: ArrayView1<f64>,
) -> Result<Vec<ExactLogLinkWorkingRow>, EstimationError> {
let rows: Vec<Result<ExactLogLinkWorkingRow, EstimationError>> = (0..eta.len())
.into_par_iter()
.map(|i| {
let geometry = exact_log_link_row(rule, i, eta[i], priorweights[i])?;
let z = if geometry.weight == 0.0 {
eta[i]
} else {
exact_working_response(i, eta[i], y[i], geometry.mu)?
};
Ok(ExactLogLinkWorkingRow { geometry, z })
})
.collect();
rows.into_iter().collect()
}
fn certify_curvature_rows(
rule: &LogLinkRule,
eta: &Array1<f64>,
priorweights: ArrayView1<f64>,
) -> Result<Vec<ExactLogLinkRow>, EstimationError> {
let rows: Vec<Result<ExactLogLinkRow, EstimationError>> = (0..eta.len())
.into_par_iter()
.map(|i| exact_log_link_row(rule, i, eta[i], priorweights[i]))
.collect();
rows.into_iter().collect()
}
pub(super) fn write_log_link_working_state(
rule: &LogLinkRule,
y: ArrayView1<f64>,
eta: &Array1<f64>,
priorweights: ArrayView1<f64>,
mu: &mut Array1<f64>,
weights: &mut Array1<f64>,
z: &mut Array1<f64>,
derivatives: Option<WorkingDerivativeBuffersMut<'_>>,
) -> Result<(), EstimationError> {
let rows = certify_working_rows(rule, y, eta, priorweights)?;
if let Some(mut derivs) = derivatives {
let slices = working_slices(mu, weights, z);
let deriv_slices = working_deriv_slices(&mut derivs);
slices
.mu
.par_iter_mut()
.zip(slices.weights.par_iter_mut())
.zip(slices.z.par_iter_mut())
.zip(deriv_slices.dmu.par_iter_mut())
.zip(deriv_slices.d2.par_iter_mut())
.zip(deriv_slices.d3.par_iter_mut())
.zip(deriv_slices.c.par_iter_mut())
.zip(deriv_slices.d.par_iter_mut())
.zip(rows.par_iter())
.for_each(
|((((((((mu_o, w_o), z_o), dmu_o), d2_o), d3_o), c_o), d_o), row)| {
*mu_o = row.geometry.mu;
*w_o = row.geometry.weight;
*z_o = row.z;
*dmu_o = row.geometry.mu;
*d2_o = row.geometry.mu;
*d3_o = row.geometry.mu;
*c_o = row.geometry.c;
*d_o = row.geometry.d;
},
);
Ok(())
} else {
let slices = working_slices(mu, weights, z);
slices
.mu
.par_iter_mut()
.zip(slices.weights.par_iter_mut())
.zip(slices.z.par_iter_mut())
.zip(rows.par_iter())
.for_each(|(((mu_o, w_o), z_o), row)| {
*mu_o = row.geometry.mu;
*w_o = row.geometry.weight;
*z_o = row.z;
});
Ok(())
}
}
pub(super) fn write_log_link_eta_curvature(
rule: &LogLinkRule,
eta: &Array1<f64>,
priorweights: ArrayView1<f64>,
mut buffers: WorkingDerivativeBuffersMut<'_>,
) -> Result<(), EstimationError> {
let rows = certify_curvature_rows(rule, eta, priorweights)?;
let slices = working_deriv_slices(&mut buffers);
slices
.c
.par_iter_mut()
.zip(slices.d.par_iter_mut())
.zip(slices.dmu.par_iter_mut())
.zip(slices.d2.par_iter_mut())
.zip(slices.d3.par_iter_mut())
.zip(rows.par_iter())
.for_each(|(((((c_o, d_o), dmu_o), d2_o), d3_o), row)| {
*c_o = row.c;
*d_o = row.d;
*dmu_o = row.mu;
*d2_o = row.mu;
*d3_o = row.mu;
});
Ok(())
}