pub(crate) use super::*;
#[test]
fn test_family_canonical_links() {
assert_eq!(Family::Poisson.canonical_link(), Link::Log);
assert_eq!(Family::Gamma.canonical_link(), Link::Inverse);
assert_eq!(Family::Binomial.canonical_link(), Link::Logit);
}
#[test]
fn test_link_functions() {
let link = Link::Log;
let mu = 5.0;
let eta = link.link(mu);
assert!((eta - mu.ln()).abs() < 1e-6);
assert!((link.inverse_link(eta) - mu).abs() < 1e-6);
let link = Link::Inverse;
let mu = 2.0;
let eta = link.link(mu);
assert!((eta - 1.0 / mu).abs() < 1e-6);
assert!((link.inverse_link(eta) - mu).abs() < 1e-6);
let link = Link::Logit;
let mu = 0.7;
let eta = link.link(mu);
assert!((link.inverse_link(eta) - mu).abs() < 1e-6);
let link = Link::Identity;
let mu = 3.0;
assert_eq!(link.link(mu), mu);
assert_eq!(link.inverse_link(mu), mu);
}
#[test]
fn test_negative_binomial_regression() {
let x = Matrix::from_vec(6, 1, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![5.0, 6.0, 7.0, 8.0, 9.0, 10.0]);
let mut model = GLM::new(Family::NegativeBinomial)
.with_dispersion(0.1)
.with_max_iter(5000); let result = model.fit(&x, &y);
assert!(
result.is_ok(),
"Negative Binomial GLM should fit, error: {:?}",
result.err()
);
assert!(model.coefficients().is_some());
assert!(model.intercept().is_some());
let predictions = model.predict(&x).expect("Predictions should succeed");
assert_eq!(predictions.len(), y.len());
}
#[test]
fn test_negative_binomial_low_dispersion() {
let x = Matrix::from_vec(6, 1, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
let mut model = GLM::new(Family::NegativeBinomial)
.with_dispersion(0.01) .with_max_iter(5000);
let result = model.fit(&x, &y);
assert!(result.is_ok(), "Low dispersion NB should converge");
assert!(model.coefficients().is_some());
}
#[test]
fn test_negative_binomial_validation() {
let x = Matrix::from_vec(3, 1, vec![1.0, 2.0, 3.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![3.0, -1.0, 5.0]);
let mut model = GLM::new(Family::NegativeBinomial);
let result = model.fit(&x, &y);
assert!(
result.is_err(),
"Negative Binomial should reject negative counts"
);
assert!(result
.expect_err("Should return error for negative counts")
.to_string()
.contains("Negative Binomial requires non-negative counts"));
}
#[test]
fn test_gamma_regression() {
let x =
Matrix::from_vec(8, 1, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![2.0, 1.5, 1.2, 1.0, 0.9, 0.8, 0.75, 0.7]);
let mut model = GLM::new(Family::Gamma); let result = model.fit(&x, &y);
assert!(result.is_ok(), "Gamma GLM should fit");
assert!(model.coefficients().is_some());
let predictions = model.predict(&x).expect("Predictions should succeed");
for &pred in predictions.as_slice() {
assert!(pred > 0.0, "Gamma predictions should be positive");
}
}
#[test]
fn test_binomial_regression() {
let x = Matrix::from_vec(8, 1, vec![-2.0, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.0])
.expect("Valid matrix");
let y = Vector::from_vec(vec![0.1, 0.15, 0.25, 0.35, 0.65, 0.75, 0.85, 0.9]);
let mut model = GLM::new(Family::Binomial); let result = model.fit(&x, &y);
assert!(result.is_ok(), "Binomial GLM should fit");
assert!(model.coefficients().is_some());
let predictions = model.predict(&x).expect("Predictions should succeed");
for &pred in predictions.as_slice() {
assert!(
(0.0..=1.0).contains(&pred),
"Binomial predictions should be in [0,1], got {pred}"
);
}
}
#[test]
fn test_poisson_invalid_response() {
let x = Matrix::from_vec(3, 1, vec![1.0, 2.0, 3.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![1.0, -2.0, 3.0]);
let mut model = GLM::new(Family::Poisson);
let result = model.fit(&x, &y);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_gamma_invalid_response() {
let x = Matrix::from_vec(3, 1, vec![1.0, 2.0, 3.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![1.0, 0.0, 3.0]);
let mut model = GLM::new(Family::Gamma);
let result = model.fit(&x, &y);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_binomial_invalid_response() {
let x = Matrix::from_vec(3, 1, vec![1.0, 2.0, 3.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.5, 1.2, 0.3]);
let mut model = GLM::new(Family::Binomial);
let result = model.fit(&x, &y);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_fit_dimension_mismatch() {
let x =
Matrix::from_vec(4, 2, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![1.0, 2.0]);
let mut model = GLM::new(Family::Poisson);
let result = model.fit(&x, &y);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::DimensionMismatch { .. }));
}
#[test]
fn test_predict_not_fitted() {
let model = GLM::new(Family::Poisson);
let x_test = Matrix::from_vec(2, 1, vec![1.0, 2.0]).expect("Valid matrix");
let result = model.predict(&x_test);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_predict_dimension_mismatch() {
let x = Matrix::from_vec(
6,
2,
vec![1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 2.0, 2.0, 3.0, 1.0, 3.0, 2.0],
)
.expect("Valid matrix");
let y = Vector::from_vec(vec![2.0, 3.0, 3.0, 4.0, 5.0, 6.0]);
let mut model = GLM::new(Family::Poisson);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(2, 1, vec![1.0, 2.0]).expect("Valid test matrix");
let result = model.predict(&x_test);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::DimensionMismatch { .. }));
}
#[test]
fn test_custom_link() {
let x = Matrix::from_vec(6, 1, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![2.0, 1.8, 1.6, 1.4, 1.2, 1.0]);
let mut model = GLM::new(Family::Gamma)
.with_link(Link::Log)
.with_max_iter(5000);
let result = model.fit(&x, &y);
assert!(
result.is_ok(),
"Custom link should work, error: {:?}",
result.err()
);
}
#[test]
fn test_builder_pattern() {
let model = GLM::new(Family::Poisson)
.with_max_iter(500)
.with_tolerance(1e-8)
.with_link(Link::Log);
assert!(model.coefficients().is_none());
assert!(model.intercept().is_none());
}