use crate::lmm::{fit_lmm, LmmWorkspace};
use crate::{ModelSpec, StartValues};
use super::common::{
assemble_varcorr, fill_se_by_predictor, nan_vcov, to_col_major, vcov_from_chol,
};
use super::{Fit, FitOptions};
#[allow(clippy::too_many_arguments)] pub(super) fn accumulate_lmm_rows(
ws: &mut LmmWorkspace,
x: &[f64],
y: &[f64],
n: usize,
p: usize,
cluster_ids: &[u32],
extra_ids: &[Vec<u32>],
weights: Option<&[f64]>,
) {
let x_mat = to_col_major(x, n, p);
ws.suff.reset();
if n > 0 && p > 0 {
ws.suff.add_rows_multi(
x_mat.as_ref().subrows(0, n),
y,
cluster_ids,
extra_ids,
weights,
);
}
}
pub(super) fn fit_lmm_into(
ws: &mut LmmWorkspace,
target_indices: &[u32],
start: Option<&StartValues>,
) -> Fit {
let lmm_fit = fit_lmm(ws, target_indices, start.map(|s| s.theta.as_slice()));
let beta = ws.fit.betas.clone();
let p = beta.len();
let sigma_sq = lmm_fit.sigma_sq;
let mut se = vec![f64::NAN; p];
fill_se_by_predictor(&ws.fit.var_diag, target_indices, &mut se);
let has_endpoint = lmm_fit.deviance.is_finite();
let tau2: Vec<f64> = if has_endpoint {
ws.theta.iter().map(|&t| t * t * sigma_sq).collect()
} else {
ws.theta.iter().map(|_| f64::NAN).collect()
};
let varcorr = if has_endpoint {
assemble_varcorr(&ws.theta, &ws.suff.groupings, sigma_sq)
} else {
vec![]
};
let vcov = if has_endpoint {
vcov_from_chol(ws.fit.factor.as_ref(), p, target_indices, sigma_sq)
} else {
nan_vcov(p)
};
let n_rows = ws.suff.n_rows;
let n_theta = ws.theta.len();
let mut fit = Fit {
beta,
se,
vcov,
tau2,
dispersion: sigma_sq,
converged: lmm_fit.converged,
varcorr,
stddev_se: vec![], aliased: vec![false; p],
n_eval: lmm_fit.n_eval,
deviance: lmm_fit.deviance,
singular: lmm_fit.boundary_hit == 1,
loglik: super::common::lmm_loglik(lmm_fit.deviance, n_rows, p),
df: if has_endpoint { p + n_theta + 1 } else { 0 },
reml: true,
fitted: vec![],
ranef: vec![],
ranef_levels: vec![],
};
fit.singular = fit.singular || fit.has_negligible_component();
fit
}
#[allow(clippy::too_many_arguments)] pub(super) fn fit_mle(
x: &[f64],
y: &[f64],
n: usize,
p: usize,
model: &ModelSpec,
cluster_ids: &[u32],
extra_ids: &[Vec<u32>],
start: Option<&StartValues>,
opts: &FitOptions,
) -> Fit {
let re = model
.re
.as_ref()
.expect("fit_mle requires a mixed model (re: Some)");
let slope_cols: Vec<usize> = re.slopes.iter().map(|&c| c as usize).collect();
let extra_slope_cols: Vec<Vec<usize>> = re
.extra_groupings
.iter()
.map(|g| g.slopes.iter().map(|&c| c as usize).collect())
.collect();
let mut ws = LmmWorkspace::for_cluster_spec_ext(p, model, n, &slope_cols, &extra_slope_cols);
let y_shifted: Vec<f64>;
let y_eff: &[f64] = match &opts.offset {
Some(o) => {
y_shifted = y.iter().zip(o).map(|(&yi, &oi)| yi - oi).collect();
&y_shifted
}
None => y,
};
accumulate_lmm_rows(
&mut ws,
x,
y_eff,
n,
p,
cluster_ids,
extra_ids,
opts.weights.as_deref(),
);
let mut fit = fit_lmm_into(&mut ws, &opts.target_indices, start);
if let Some(w) = &opts.weights {
fit.deviance -= w.iter().map(|v| v.ln()).sum::<f64>();
fit.loglik = super::common::lmm_loglik(fit.deviance, n, p);
}
fit
}
#[cfg(test)]
#[allow(clippy::too_many_arguments)] pub(crate) fn fit_mle_noz_pub(
x: &[f64],
y: &[f64],
n: usize,
p: usize,
sized: &ModelSpec,
cluster_ids: &[u32],
extra_ids: &[Vec<u32>],
start: Option<&StartValues>,
opts: &FitOptions,
) -> Fit {
fit_mle(x, y, n, p, sized, cluster_ids, extra_ids, start, opts)
}