use super::*;
use crate::inference::dispersion_cov::se_from_covariance;
pub(crate) const REML_SECOND_ORDER_RHO_CAP: usize = 4;
pub(crate) const REML_CONTINUATION_PREWARM_RHO_CAP: usize = 4;
pub(crate) const REML_SEED_SCREENING_RHO_CAP: usize = 4;
const KAHAN_SWITCH_ELEMS: usize = 10_000;
pub(crate) fn faer_frob_inner(a: MatRef<'_, f64>, b: MatRef<'_, f64>) -> f64 {
let (m, n) = (a.nrows(), a.ncols());
let elem_count = m.saturating_mul(n);
if elem_count < KAHAN_SWITCH_ELEMS {
let mut sum = 0.0_f64;
for j in 0..n {
for i in 0..m {
sum += a[(i, j)] * b[(i, j)];
}
}
sum
} else {
let mut sum = KahanSum::default();
for j in 0..n {
for i in 0..m {
sum.add(a[(i, j)] * b[(i, j)]);
}
}
sum.sum()
}
}
pub(crate) fn kahan_sum<I>(iter: I) -> f64
where
I: IntoIterator<Item = f64>,
{
let mut acc = KahanSum::default();
for value in iter {
acc.add(value);
}
acc.sum()
}
#[derive(Clone, Debug)]
pub(crate) struct ParametricColumnConditioning {
pub(crate) intercept_idx: Option<usize>,
pub(crate) columns: Vec<(usize, f64, f64)>,
}
impl ParametricColumnConditioning {
pub(crate) fn from_column_indices(x: &DesignMatrix, unpenalized_cols: &[usize]) -> Self {
const SCALE_EPS: f64 = 1e-12;
let n = x.nrows();
if n == 0 {
return Self {
intercept_idx: None,
columns: Vec::new(),
};
}
let mut intercept_idx = None;
let mut columns = Vec::new();
let block = x.extract_columns(unpenalized_cols);
for (k, &j) in unpenalized_cols.iter().enumerate() {
let col = block.column(k);
let first = col[0];
let is_constant = col.iter().all(|&v| (v - first).abs() <= 1e-12);
if is_constant {
if (first - 1.0).abs() <= 1e-12 && intercept_idx.is_none() {
intercept_idx = Some(j);
}
continue;
}
let mean = col.iter().copied().sum::<f64>() / n as f64;
let var = col
.iter()
.map(|&v| {
let d = v - mean;
d * d
})
.sum::<f64>()
/ n as f64;
if !var.is_finite() || var <= SCALE_EPS * SCALE_EPS {
continue;
}
columns.push((j, mean, var.sqrt()));
}
if intercept_idx.is_none() {
for (_, mean, _) in &mut columns {
*mean = 0.0;
}
}
Self {
intercept_idx,
columns,
}
}
pub(crate) fn infer_from_penalty_specs(x: &DesignMatrix, specs: &[PenaltySpec]) -> Self {
let p = x.ncols();
let mut penalized = vec![false; p];
for spec in specs {
let range = spec.col_range(p);
for j in range {
penalized[j] = true;
}
}
let unpenalized: Vec<usize> = (0..p).filter(|&j| !penalized[j]).collect();
Self::from_column_indices(x, &unpenalized)
}
pub(crate) fn is_active(&self) -> bool {
!self.columns.is_empty()
}
pub(crate) fn apply_to_design(&self, x: &DesignMatrix) -> DesignMatrix {
if !self.is_active() {
return x.clone();
}
DesignMatrix::Dense(crate::matrix::DenseDesignMatrix::from(Arc::new(
crate::matrix::ConditionedDesign::new(x.clone(), self.columns.clone()),
)))
}
pub(crate) fn transform_constraint_matrix_to_internal(
&self,
a_original: &Array2<f64>,
) -> Array2<f64> {
self.transform_matrix_columnswith_a(a_original)
}
pub(crate) fn transform_linear_constraints_to_internal(
&self,
constraints: Option<crate::pirls::LinearInequalityConstraints>,
) -> Option<crate::pirls::LinearInequalityConstraints> {
constraints.map(|constraints| crate::pirls::LinearInequalityConstraints {
a: self.transform_constraint_matrix_to_internal(&constraints.a),
b: constraints.b,
})
}
pub(crate) fn backtransform_beta(&self, beta_internal: &Array1<f64>) -> Array1<f64> {
let mut beta = beta_internal.clone();
for &(j, mean, scale) in &self.columns {
if let Some(intercept_idx) = self.intercept_idx {
beta[intercept_idx] -= beta_internal[j] * mean / scale;
}
beta[j] = beta_internal[j] / scale;
}
beta
}
pub(crate) fn transform_matrix_columnswith_a(&self, mat: &Array2<f64>) -> Array2<f64> {
let mut out = mat.clone();
self.transform_matrix_columnswith_a_inplace(&mut out);
out
}
pub(crate) fn transform_matrix_columnswith_a_inplace(&self, mat: &mut Array2<f64>) {
if !self.is_active() {
return;
}
let intercept_col = self.intercept_idx.map(|idx| mat.column(idx).to_owned());
for &(j, mean, scale) in &self.columns {
let mut target = mat.column_mut(j);
if mean != 0.0
&& let Some(intercept_col) = intercept_col.as_ref()
{
target -= &(intercept_col * mean);
}
if scale != 1.0 {
target.mapv_inplace(|v| v / scale);
}
}
}
pub(crate) fn left_multiply_by_m(&self, mat_internal: &Array2<f64>) -> Array2<f64> {
let mut out = mat_internal.clone();
if !self.is_active() {
return out;
}
if let Some(intercept_idx) = self.intercept_idx {
for &(j, mean, scale) in &self.columns {
if mean != 0.0 {
let factor = mean / scale;
let row_j_snapshot = mat_internal.row(j).to_owned();
let mut interceptrow = out.row_mut(intercept_idx);
interceptrow -= &(&row_j_snapshot * factor);
}
}
}
for &(j, _mean, scale) in &self.columns {
if scale != 1.0 {
out.row_mut(j).mapv_inplace(|v| v / scale);
}
}
out
}
pub(crate) fn right_multiply_by_m_transpose(&self, mat_internal: &Array2<f64>) -> Array2<f64> {
let mut out = mat_internal.clone();
if !self.is_active() {
return out;
}
if let Some(intercept_idx) = self.intercept_idx {
for &(j, mean, scale) in &self.columns {
if mean != 0.0 {
let factor = mean / scale;
let col_j_snapshot = mat_internal.column(j).to_owned();
let mut intercept_col = out.column_mut(intercept_idx);
intercept_col -= &(&col_j_snapshot * factor);
}
}
}
for &(j, _mean, scale) in &self.columns {
if scale != 1.0 {
out.column_mut(j).mapv_inplace(|v| v / scale);
}
}
out
}
pub(crate) fn left_multiply_by_m_inv_transpose(
&self,
mat_internal: &Array2<f64>,
) -> Array2<f64> {
let mut out = mat_internal.clone();
if !self.is_active() {
return out;
}
if let Some(intercept_idx) = self.intercept_idx {
let interceptrow_snapshot = mat_internal.row(intercept_idx).to_owned();
for &(j, mean, scale) in &self.columns {
if scale != 1.0 {
out.row_mut(j).mapv_inplace(|v| v * scale);
}
if mean != 0.0 {
let mut row_j = out.row_mut(j);
row_j += &(&interceptrow_snapshot * mean);
}
}
} else {
for &(j, _mean, scale) in &self.columns {
if scale != 1.0 {
out.row_mut(j).mapv_inplace(|v| v * scale);
}
}
}
out
}
pub(crate) fn right_multiply_by_m_inv(&self, mat_internal: &Array2<f64>) -> Array2<f64> {
let mut out = mat_internal.clone();
if !self.is_active() {
return out;
}
if let Some(intercept_idx) = self.intercept_idx {
let intercept_col_snapshot = mat_internal.column(intercept_idx).to_owned();
for &(j, mean, scale) in &self.columns {
if scale != 1.0 {
out.column_mut(j).mapv_inplace(|v| v * scale);
}
if mean != 0.0 {
let mut col_j = out.column_mut(j);
col_j += &(&intercept_col_snapshot * mean);
}
}
} else {
for &(j, _mean, scale) in &self.columns {
if scale != 1.0 {
out.column_mut(j).mapv_inplace(|v| v * scale);
}
}
}
out
}
pub(crate) fn backtransform_covariance(&self, cov_internal: &Array2<f64>) -> Array2<f64> {
let right = self.right_multiply_by_m_transpose(cov_internal);
self.left_multiply_by_m(&right)
}
pub(crate) fn backtransform_penalized_hessian(&self, h_internal: &Array2<f64>) -> Array2<f64> {
let right = self.right_multiply_by_m_inv(h_internal);
self.left_multiply_by_m_inv_transpose(&right)
}
pub(crate) fn backtransform_external_result(
&self,
mut result: ExternalOptimResult,
) -> ExternalOptimResult {
if !self.is_active() {
return result;
}
result.beta = self.backtransform_beta(&result.beta);
if let Some(inf) = result.inference.as_mut() {
inf.penalized_hessian = self
.backtransform_penalized_hessian(inf.penalized_hessian.as_array())
.into();
inf.beta_covariance = inf
.beta_covariance
.take()
.map(|cov| self.backtransform_covariance(cov.as_array()).into());
inf.beta_standard_errors = inf
.beta_covariance
.as_ref()
.map(|c| se_from_covariance(c.as_array()));
inf.beta_covariance_corrected = inf
.beta_covariance_corrected
.take()
.map(|cov| self.backtransform_covariance(&cov));
inf.beta_standard_errors_corrected = inf
.beta_covariance_corrected
.as_ref()
.map(se_from_covariance);
inf.beta_covariance_frequentist = inf
.beta_covariance_frequentist
.take()
.map(|cov| self.backtransform_covariance(&cov));
inf.coefficient_influence = None;
inf.weighted_gram = None;
inf.bias_correction_beta = inf
.bias_correction_beta
.take()
.map(|b| self.backtransform_beta(&b));
inf.smoothing_correction = inf
.smoothing_correction
.take()
.map(|cov| self.backtransform_covariance(&cov));
inf.reparam_qs = None;
}
result.constraint_kkt = None;
result
}
}
pub(crate) fn map_hessian_to_original_basis(
pirls: &crate::pirls::PirlsResult,
) -> Result<Array2<f64>, EstimationError> {
let qs = &pirls.reparam_result.qs;
let h_t = &pirls.penalized_hessian_transformed;
let tmp = h_t.left_dot_matrix(qs);
let mut h = tmp.dot(&qs.t());
crate::families::custom_family::symmetrize_dense_in_place(&mut h);
Ok(h)
}
#[inline]
pub(crate) fn scaled_covariance(cov: Array2<f64>, phi: f64) -> Array2<f64> {
if (phi - 1.0).abs() <= f64::EPSILON {
cov
} else {
cov * phi
}
}