use super::*;
use crate::{Family, GroupIds, ModelSpec};
#[test]
fn fit_ols_recovers_slope() {
let n = 20;
let p = 2;
let x: Vec<f64> = (0..n).flat_map(|i| [1.0, i as f64]).collect(); let y: Vec<f64> = (0..n).map(|i| 2.0 * i as f64).collect();
let model = ModelSpec {
family: Family::Gaussian,
re: None,
};
let f = fit_cold(
&x,
&y,
n,
p,
&model,
&GroupIds::default(),
&FitOptions {
target_indices: vec![1],
..FitOptions::default()
},
);
assert!(f.converged);
assert!((f.beta[1] - 2.0).abs() < 1e-6);
assert!(f.deviance.is_nan());
assert!(!f.singular);
assert!(f.tau2.is_empty());
assert!(f.dispersion >= 0.0 && f.dispersion < 1e-9);
}
#[test]
fn fit_ols_weighted_matches_r_lm() {
let xv = [
0.2, 1.4, -0.8, 2.1, 0.5, -1.3, 1.9, 0.0, -0.6, 1.1, 2.4, -1.7,
];
let w = vec![1.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 4.0, 2.0, 1.0, 3.0, 2.0];
let y = vec![
0.8, 3.1, -1.2, 4.6, 1.4, -2.0, 4.1, 0.3, -0.9, 2.6, 5.2, -3.1,
];
let n = 12;
let mut x = Vec::with_capacity(n * 2);
for &xi in &xv {
x.extend_from_slice(&[1.0, xi]);
}
const REF_BETA: [f64; 2] = [0.371528122456273, 1.996237765292144];
const REF_SE: [f64; 2] = [0.0289002251717619, 0.0195893362923423];
const REF_VCOV_01: f64 = -0.0002002132676736411;
let model = ModelSpec {
family: Family::Gaussian,
re: None,
};
let opts = FitOptions {
target_indices: vec![0, 1],
weights: Some(w),
..FitOptions::default()
};
let f = fit_cold(&x, &y, n, 2, &model, &GroupIds::default(), &opts);
assert!(f.converged);
for j in 0..2 {
assert!((f.beta[j] - REF_BETA[j]).abs() < 1e-9, "beta[{j}]");
assert!((f.se[j] - REF_SE[j]).abs() < 1e-9, "se[{j}]");
}
for (i, j) in [(0, 1), (1, 0)] {
assert!(
(f.vcov[i][j] - REF_VCOV_01).abs() < 1e-12,
"vcov[{i}][{j}] = {}, R = {REF_VCOV_01}",
f.vcov[i][j]
);
}
const REF_LOGLIK: f64 = 11.7602761183173;
assert!(
(f.loglik - REF_LOGLIK).abs() < 1e-9,
"loglik {} vs R {REF_LOGLIK}",
f.loglik
);
assert_eq!(f.df, 3);
assert!(!f.reml);
assert_eq!(f.fitted.len(), n);
for (i, &xi) in xv.iter().enumerate() {
let eta = f.beta[0] + f.beta[1] * xi;
assert!((f.fitted[i] - eta).abs() < 1e-12, "fitted[{i}]");
}
}
#[test]
fn fit_ols_offset_matches_r_lm() {
let xv = [
0.2, 1.4, -0.8, 2.1, 0.5, -1.3, 1.9, 0.0, -0.6, 1.1, 2.4, -1.7,
];
let w = vec![1.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 4.0, 2.0, 1.0, 3.0, 2.0];
let y = vec![
0.8, 3.1, -1.2, 4.6, 1.4, -2.0, 4.1, 0.3, -0.9, 2.6, 5.2, -3.1,
];
let n = 12;
let o: Vec<f64> = (0..n).map(|i| 0.3 * (i % 4) as f64).collect();
let mut x = Vec::with_capacity(n * 2);
for &xi in &xv {
x.extend_from_slice(&[1.0, xi]);
}
const REF_BETA: [f64; 2] = [-0.159548531835057, 1.964134686017193];
const REF_LOGLIK: f64 = -5.65572764128147;
let model = ModelSpec {
family: Family::Gaussian,
re: None,
};
let opts = FitOptions {
target_indices: vec![0, 1],
weights: Some(w),
offset: Some(o.clone()),
..FitOptions::default()
};
let f = fit_cold(&x, &y, n, 2, &model, &GroupIds::default(), &opts);
assert!(f.converged);
for (j, (&b, &r)) in f.beta.iter().zip(&REF_BETA).enumerate() {
assert!((b - r).abs() < 1e-9, "beta[{j}] {b} vs R {r}");
}
assert!(
(f.loglik - REF_LOGLIK).abs() < 1e-9,
"loglik {} vs R {REF_LOGLIK}",
f.loglik
);
for (i, &xi) in xv.iter().enumerate() {
let eta = o[i] + f.beta[0] + f.beta[1] * xi;
assert!((f.fitted[i] - eta).abs() < 1e-12, "fitted[{i}]");
}
let f_none = fit_cold(
&x,
&y,
n,
2,
&model,
&GroupIds::default(),
&FitOptions {
offset: None,
..opts.clone()
},
);
let f_zero = fit_cold(
&x,
&y,
n,
2,
&model,
&GroupIds::default(),
&FitOptions {
offset: Some(vec![0.0; n]),
..opts
},
);
assert_eq!(f_none.beta, f_zero.beta);
assert_eq!(f_none.loglik.to_bits(), f_zero.loglik.to_bits());
}
#[test]
fn fit_ols_constant_weights_invariant() {
let xv = [0.2, 1.4, -0.8, 2.1, 0.5, -1.3, 1.9, 0.0];
let y: Vec<f64> = xv
.iter()
.enumerate()
.map(|(i, v)| 1.0 + 2.0 * v + if i == 0 { 0.01 } else { 0.0 })
.collect();
let n = 8;
let mut x = Vec::with_capacity(n * 2);
for &xi in &xv {
x.extend_from_slice(&[1.0, xi]);
}
let model = ModelSpec {
family: Family::Gaussian,
re: None,
};
let base = FitOptions {
target_indices: vec![0, 1],
..FitOptions::default()
};
let f0 = fit_cold(&x, &y, n, 2, &model, &GroupIds::default(), &base);
let opts = FitOptions {
weights: Some(vec![3.0; n]),
..base
};
let f1 = fit_cold(&x, &y, n, 2, &model, &GroupIds::default(), &opts);
for j in 0..2 {
assert!((f0.beta[j] - f1.beta[j]).abs() < 1e-12);
assert!((f0.se[j] - f1.se[j]).abs() < 1e-12);
}
}