pub(crate) use super::*;
#[test]
fn test_new() {
let model = BayesianLogisticRegression::new(1.0);
assert!(model.coefficients_map.is_none());
assert!(model.posterior_covariance.is_none());
}
#[test]
fn test_builder_pattern() {
let model = BayesianLogisticRegression::new(1.0)
.with_learning_rate(0.1)
.with_max_iter(500)
.with_tolerance(1e-3);
assert!(model.coefficients_map.is_none());
}
#[test]
fn test_fit_simple() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
let result = model.fit(&x, &y);
assert!(result.is_ok(), "Fit should succeed");
assert!(model.coefficients_map.is_some());
assert!(model.posterior_covariance.is_some());
let beta = model
.coefficients_map
.as_ref()
.expect("MAP estimate exists");
assert!(
beta[0] > 0.0,
"Coefficient should be positive, got {}",
beta[0]
);
}
#[test]
fn test_predict_proba() {
let x = Matrix::from_vec(4, 1, vec![-1.0, -0.5, 0.5, 1.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(3, 1, vec![-2.0, 0.0, 2.0]).expect("Valid test matrix");
let probas = model.predict_proba(&x_test).expect("Prediction succeeds");
assert_eq!(probas.len(), 3);
for &p in probas.as_slice() {
assert!(
(0.0..=1.0).contains(&p),
"Probability should be in [0,1], got {p}"
);
}
assert!(probas[0] < probas[1], "P(y=1 | x=-2) < P(y=1 | x=0)");
assert!(probas[1] < probas[2], "P(y=1 | x=0) < P(y=1 | x=2)");
}
#[test]
fn test_predict() {
let x = Matrix::from_vec(4, 1, vec![-1.0, -0.5, 0.5, 1.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(2, 1, vec![-2.0, 2.0]).expect("Valid test matrix");
let labels = model.predict(&x_test).expect("Prediction succeeds");
assert_eq!(labels.len(), 2);
for &label in labels.as_slice() {
assert!(
label == 0.0 || label == 1.0,
"Label should be 0 or 1, got {label}"
);
}
}
#[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![0.0, 1.0]);
let mut model = BayesianLogisticRegression::new(1.0);
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_fit_invalid_labels() {
let x = Matrix::from_vec(3, 1, vec![1.0, 2.0, 3.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.5, 1.0]);
let mut model = BayesianLogisticRegression::new(1.0);
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_predict_not_fitted() {
let model = BayesianLogisticRegression::new(1.0);
let x_test = Matrix::from_vec(2, 1, vec![1.0, 2.0]).expect("Valid matrix");
let result = model.predict_proba(&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(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![0.0, 0.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(1.0);
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_proba(&x_test);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::DimensionMismatch { .. }));
}
#[test]
fn test_map_convergence() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1)
.with_max_iter(2000)
.with_tolerance(1e-5);
let result = model.fit(&x, &y);
assert!(result.is_ok(), "MAP estimation should converge");
}
#[test]
fn test_map_non_convergence() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1)
.with_max_iter(5) .with_tolerance(1e-10);
let result = model.fit(&x, &y);
assert!(result.is_err(), "Should fail to converge");
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_predict_proba_interval() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(3, 1, vec![-2.0, 0.0, 2.0]).expect("Valid test matrix");
let (lower, upper) = model
.predict_proba_interval(&x_test, 0.95)
.expect("Interval prediction succeeds");
assert_eq!(lower.len(), 3);
assert_eq!(upper.len(), 3);
let probas = model.predict_proba(&x_test).expect("Prediction succeeds");
for i in 0..3 {
assert!(
lower[i] <= probas[i],
"Lower bound should be <= point estimate: {i}: {} <= {}",
lower[i],
probas[i]
);
assert!(
probas[i] <= upper[i],
"Upper bound should be >= point estimate: {i}: {} >= {}",
probas[i],
upper[i]
);
assert!(
lower[i] >= 0.0 && lower[i] <= 1.0,
"Lower bound should be in [0,1], got {}",
lower[i]
);
assert!(
upper[i] >= 0.0 && upper[i] <= 1.0,
"Upper bound should be in [0,1], got {}",
upper[i]
);
}
for i in 0..3 {
assert!(
upper[i] >= lower[i],
"Upper bound should be >= lower bound at {i}: {} >= {}",
upper[i],
lower[i]
);
}
let max_width = (0..3).map(|i| upper[i] - lower[i]).fold(0.0_f32, f32::max);
assert!(
max_width > 0.01,
"At least one interval should have width > 0.01, max was {max_width}"
);
}
#[test]
fn test_predict_interval_not_fitted() {
let model = BayesianLogisticRegression::new(1.0);
let x_test = Matrix::from_vec(2, 1, vec![1.0, 2.0]).expect("Valid matrix");
let result = model.predict_proba_interval(&x_test, 0.95);
assert!(result.is_err());
let err = result.expect_err("Should be an error");
assert!(matches!(err, AprenderError::Other(_)));
}
#[test]
fn test_sigmoid_extreme_values() {
let sig_neg = BayesianLogisticRegression::sigmoid(-100.0);
assert!(sig_neg < 1e-10);
let sig_pos = BayesianLogisticRegression::sigmoid(100.0);
assert!(sig_pos > 0.9999999);
let sig_zero = BayesianLogisticRegression::sigmoid(0.0);
assert!((sig_zero - 0.5).abs() < 1e-6);
}
#[test]
fn test_prior_precision_effects() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model_low = BayesianLogisticRegression::new(0.1);
model_low.fit(&x, &y).expect("Fit succeeds");
let mut model_high = BayesianLogisticRegression::new(10.0);
model_high.fit(&x, &y).expect("Fit succeeds");
let beta_low = model_low
.coefficients_map
.as_ref()
.expect("has coefficients");
let beta_high = model_high
.coefficients_map
.as_ref()
.expect("has coefficients");
assert!(
beta_low[0].abs() >= beta_high[0].abs(),
"Higher prior precision should shrink coefficients"
);
}
#[test]
fn test_multiple_features() {
let x = Matrix::from_vec(4, 2, vec![1.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, -1.0])
.expect("Valid matrix");
let y = Vector::from_vec(vec![1.0, 1.0, 0.0, 0.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let beta = model.coefficients_map.as_ref().unwrap();
assert!(beta.len() >= 2);
}
#[test]
fn test_predict_interval_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![0.0, 0.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(1.0);
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_proba_interval(&x_test, 0.95);
assert!(result.is_err());
}
#[test]
fn test_predict_returns_labels() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(4, 1, vec![-3.0, -0.1, 0.1, 3.0]).expect("Valid test matrix");
let labels = model.predict(&x_test).expect("Prediction succeeds");
for &label in labels.as_slice() {
assert!(label == 0.0 || label == 1.0);
}
assert_eq!(labels[0], 0.0);
assert_eq!(labels[3], 1.0);
}
#[test]
fn test_wide_credible_interval() {
let x = Matrix::from_vec(6, 1, vec![-2.0, -1.0, -0.5, 0.5, 1.0, 2.0]).expect("Valid matrix");
let y = Vector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
let mut model = BayesianLogisticRegression::new(0.1);
model.fit(&x, &y).expect("Fit succeeds");
let x_test = Matrix::from_vec(1, 1, vec![0.0]).expect("Valid test matrix");
let (lower_90, upper_90) = model
.predict_proba_interval(&x_test, 0.90)
.expect("Interval succeeds");
let (lower_99, upper_99) = model
.predict_proba_interval(&x_test, 0.99)
.expect("Interval succeeds");
let width_90 = upper_90[0] - lower_90[0];
let width_99 = upper_99[0] - lower_99[0];
assert!(
width_99 >= width_90,
"99% CI should be wider than 90% CI: {} >= {}",
width_99,
width_90
);
}