use antecedent_core::ExecutionContext;
use crate::diagnostics::InferenceDiagnostics;
use crate::error::ProbError;
use crate::posterior::{PosteriorDraws, PosteriorSchema};
use crate::prior::PriorSet;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum BayesLikelihood {
GaussianIdentity,
BernoulliLogit,
BernoulliProbit,
PoissonLog,
}
#[derive(Clone, Copy, Debug)]
pub struct BayesDesignRef<'a> {
pub x_colmajor: &'a [f64],
pub nrows: usize,
pub ncols: usize,
pub y: &'a [f64],
pub weights: Option<&'a [f64]>,
pub offsets: Option<&'a [f64]>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BayesFitOptions {
pub n_draws: usize,
pub max_iter: u32,
pub grad_tol: f64,
pub seed: u64,
}
impl Default for BayesFitOptions {
fn default() -> Self {
Self { n_draws: 1000, max_iter: 50, grad_tol: 1e-8, seed: 0 }
}
}
#[derive(Clone, Debug)]
pub struct BayesFitResult {
pub draws: PosteriorDraws,
pub map: Vec<f64>,
pub diagnostics: InferenceDiagnostics,
pub cov: Option<Vec<f64>>,
}
pub trait InferenceBackend: Send + Sync {
fn fit(
&self,
likelihood: BayesLikelihood,
design: BayesDesignRef<'_>,
prior: &PriorSet,
options: &BayesFitOptions,
workspace: &mut LaplaceWorkspace,
ctx: &ExecutionContext,
) -> Result<BayesFitResult, ProbError>;
}
#[derive(Clone, Debug, Default)]
pub struct LaplaceWorkspace {
pub grad: Vec<f64>,
pub neg_hessian: Vec<f64>,
pub factor: Vec<f64>,
pub step: Vec<f64>,
pub beta: Vec<f64>,
pub q: Vec<f64>,
pub p: Vec<f64>,
pub eta: Vec<f64>,
pub work_w: Vec<f64>,
pub draw_scratch: Vec<f64>,
pub grow_count: u32,
pub(crate) conjugate_xtx: Vec<f64>,
pub(crate) conjugate_xty: Vec<f64>,
pub(crate) conjugate_yty: f64,
pub(crate) conjugate_n_eff: f64,
pub(crate) conjugate_key: Option<ConjugateGramKey>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ConjugateGramKey {
x: usize,
y: usize,
weights: usize,
offsets: usize,
nrows: usize,
ncols: usize,
}
impl ConjugateGramKey {
pub(crate) fn from_design(design: &BayesDesignRef<'_>) -> Self {
Self {
x: design.x_colmajor.as_ptr() as usize,
y: design.y.as_ptr() as usize,
weights: design.weights.map_or(0, |w| w.as_ptr() as usize),
offsets: design.offsets.map_or(0, |o| o.as_ptr() as usize),
nrows: design.nrows,
ncols: design.ncols,
}
}
}
impl LaplaceWorkspace {
pub fn prepare(&mut self, nrows: usize, ncols: usize, n_draws: usize) {
let mut grew = false;
grew |= resize_min(&mut self.grad, ncols);
grew |= resize_min(&mut self.neg_hessian, ncols.saturating_mul(ncols));
grew |= resize_min(&mut self.factor, ncols.saturating_mul(ncols));
grew |= resize_min(&mut self.step, ncols);
grew |= resize_min(&mut self.beta, ncols);
grew |= resize_min(&mut self.q, ncols);
grew |= resize_min(&mut self.p, ncols);
grew |= resize_min(&mut self.eta, nrows);
grew |= resize_min(&mut self.work_w, nrows);
let draw_need = n_draws.saturating_mul(ncols).max(ncols);
grew |= resize_min(&mut self.draw_scratch, draw_need);
if grew {
self.grow_count = self.grow_count.saturating_add(1);
}
}
pub fn zero_numeric(&mut self) {
for v in [
&mut self.grad,
&mut self.neg_hessian,
&mut self.factor,
&mut self.step,
&mut self.beta,
&mut self.q,
&mut self.p,
&mut self.eta,
&mut self.work_w,
&mut self.draw_scratch,
] {
for x in v.iter_mut() {
*x = 0.0;
}
}
}
}
fn resize_min(buf: &mut Vec<f64>, need: usize) -> bool {
if buf.len() < need {
buf.resize(need, 0.0);
true
} else {
false
}
}
#[must_use]
pub fn coefficient_schema(ncols: usize) -> PosteriorSchema {
PosteriorSchema::coefficients(ncols)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workspace_reuses_buffers() {
let mut ws = LaplaceWorkspace::default();
ws.prepare(100, 5, 200);
let g1 = ws.grow_count;
assert!(g1 >= 1);
ws.prepare(100, 5, 200);
assert_eq!(ws.grow_count, g1, "second prepare must not grow");
ws.prepare(200, 5, 200);
assert!(ws.grow_count > g1);
}
}