use ferrolearn_core::introspection::HasCoefficients;
use ferrolearn_core::{Fit, Predict};
use ferrolearn_test_oracle::{
MIN_LOGISTIC_ACCURACY, TOL_ITERATIVE_LINEAR_ABS, TOL_ITERATIVE_LINEAR_REL, TOL_LINEAR_FIT_ABS,
TOL_LINEAR_FIT_REL, TOL_LOGISTIC_ABS, TOL_LOGISTIC_REL, assert_close, assert_close_slice,
json_to_array1, json_to_array2, json_to_labels, load_fixture,
};
#[test]
fn conformance_linear_regression() {
let fx = load_fixture("linear_regression");
let x = json_to_array2(&fx.input["X"]);
let y = json_to_array1(&fx.input["y"]);
let (rel, abs) = fx.tolerance(TOL_LINEAR_FIT_REL, TOL_LINEAR_FIT_ABS);
let fit_intercept = fx.params["fit_intercept"].as_bool().unwrap_or(true);
let model = ferrolearn_linear::LinearRegression::<f64>::new().with_fit_intercept(fit_intercept);
let fitted = model.fit(&x, &y).expect("LinearRegression fit");
let expected_coefs = json_to_array1(&fx.expected["coefficients"]);
assert_close_slice(
fitted.coefficients().as_slice().unwrap(),
expected_coefs.as_slice().unwrap(),
rel,
abs,
"LinearRegression.coefficients",
);
let expected_intercept = fx.expected["intercept"].as_f64().unwrap();
assert_close(
fitted.intercept(),
expected_intercept,
rel,
abs,
"LinearRegression.intercept",
);
let preds = fitted.predict(&x).expect("LinearRegression predict");
let expected_preds = json_to_array1(&fx.expected["predictions"]);
assert_close_slice(
preds.as_slice().unwrap(),
expected_preds.as_slice().unwrap(),
rel,
abs,
"LinearRegression.predict",
);
}
#[test]
fn conformance_ridge() {
let fx = load_fixture("ridge");
let x = json_to_array2(&fx.input["X"]);
let y = json_to_array1(&fx.input["y"]);
let (rel, abs) = fx.tolerance(TOL_LINEAR_FIT_REL, TOL_LINEAR_FIT_ABS);
let alpha = fx.params["alpha"].as_f64().unwrap_or(1.0);
let fit_intercept = fx.params["fit_intercept"].as_bool().unwrap_or(true);
let model = ferrolearn_linear::Ridge::<f64>::new()
.with_alpha(alpha)
.with_fit_intercept(fit_intercept);
let fitted = model.fit(&x, &y).expect("Ridge fit");
let expected_coefs = json_to_array1(&fx.expected["coefficients"]);
assert_close_slice(
fitted.coefficients().as_slice().unwrap(),
expected_coefs.as_slice().unwrap(),
rel,
abs,
"Ridge.coefficients",
);
let expected_intercept = fx.expected["intercept"].as_f64().unwrap();
assert_close(
fitted.intercept(),
expected_intercept,
rel,
abs,
"Ridge.intercept",
);
let preds = fitted.predict(&x).expect("Ridge predict");
let expected_preds = json_to_array1(&fx.expected["predictions"]);
assert_close_slice(
preds.as_slice().unwrap(),
expected_preds.as_slice().unwrap(),
rel,
abs,
"Ridge.predict",
);
}
#[test]
fn conformance_lasso() {
let fx = load_fixture("lasso");
let x = json_to_array2(&fx.input["X"]);
let y = json_to_array1(&fx.input["y"]);
let (rel, abs) = fx.tolerance(TOL_ITERATIVE_LINEAR_REL, TOL_ITERATIVE_LINEAR_ABS);
let alpha = fx.params["alpha"].as_f64().unwrap_or(0.1);
let fit_intercept = fx.params["fit_intercept"].as_bool().unwrap_or(true);
let model = ferrolearn_linear::Lasso::<f64>::new()
.with_alpha(alpha)
.with_fit_intercept(fit_intercept);
let fitted = model.fit(&x, &y).expect("Lasso fit");
let expected_coefs = json_to_array1(&fx.expected["coefficients"]);
assert_close_slice(
fitted.coefficients().as_slice().unwrap(),
expected_coefs.as_slice().unwrap(),
rel,
abs,
"Lasso.coefficients",
);
let expected_intercept = fx.expected["intercept"].as_f64().unwrap();
assert_close(
fitted.intercept(),
expected_intercept,
rel,
abs,
"Lasso.intercept",
);
let preds = fitted.predict(&x).expect("Lasso predict");
let expected_preds = json_to_array1(&fx.expected["predictions"]);
assert_close_slice(
preds.as_slice().unwrap(),
expected_preds.as_slice().unwrap(),
rel,
abs,
"Lasso.predict",
);
}
#[test]
fn conformance_elastic_net() {
let fx = load_fixture("elastic_net");
let x = json_to_array2(&fx.input["X"]);
let y = json_to_array1(&fx.input["y"]);
let (rel, abs) = fx.tolerance(TOL_ITERATIVE_LINEAR_REL, TOL_ITERATIVE_LINEAR_ABS);
let alpha = fx.params["alpha"].as_f64().unwrap_or(0.1);
let l1_ratio = fx.params["l1_ratio"].as_f64().unwrap_or(0.5);
let fit_intercept = fx.params["fit_intercept"].as_bool().unwrap_or(true);
let model = ferrolearn_linear::ElasticNet::<f64>::new()
.with_alpha(alpha)
.with_l1_ratio(l1_ratio)
.with_fit_intercept(fit_intercept);
let fitted = model.fit(&x, &y).expect("ElasticNet fit");
let expected_coefs = json_to_array1(&fx.expected["coefficients"]);
assert_close_slice(
fitted.coefficients().as_slice().unwrap(),
expected_coefs.as_slice().unwrap(),
rel,
abs,
"ElasticNet.coefficients",
);
let expected_intercept = fx.expected["intercept"].as_f64().unwrap();
assert_close(
fitted.intercept(),
expected_intercept,
rel,
abs,
"ElasticNet.intercept",
);
let preds = fitted.predict(&x).expect("ElasticNet predict");
let expected_preds = json_to_array1(&fx.expected["predictions"]);
assert_close_slice(
preds.as_slice().unwrap(),
expected_preds.as_slice().unwrap(),
rel,
abs,
"ElasticNet.predict",
);
}
#[test]
fn conformance_logistic_regression() {
let fx = load_fixture("logistic_regression");
let x = json_to_array2(&fx.input["X"]);
let y = json_to_array1(&fx.input["y"]);
let (rel, abs) = fx.tolerance(TOL_LOGISTIC_REL, TOL_LOGISTIC_ABS);
let c = fx.params["C"].as_f64().unwrap_or(1.0);
let fit_intercept = fx.params["fit_intercept"].as_bool().unwrap_or(true);
let model = ferrolearn_linear::LogisticRegression::<f64>::new()
.with_c(c)
.with_fit_intercept(fit_intercept);
let y_usize: ndarray::Array1<usize> = y.iter().map(|&v| v as usize).collect();
let fitted = model.fit(&x, &y_usize).expect("LogisticRegression fit");
let preds = fitted.predict(&x).expect("LogisticRegression predict");
let preds_i64: Vec<i64> = preds.iter().map(|&v| v as i64).collect();
let expected_classes = json_to_labels(&fx.expected["predicted_classes"]);
assert_eq!(
preds_i64.len(),
expected_classes.len(),
"LogisticRegression.predict: length mismatch"
);
let matches = preds_i64
.iter()
.zip(expected_classes.iter())
.filter(|(a, e)| a == e)
.count();
let accuracy = matches as f64 / preds_i64.len() as f64;
assert!(
accuracy >= MIN_LOGISTIC_ACCURACY,
"LogisticRegression.predict label-agreement {accuracy:.4} below floor \
{MIN_LOGISTIC_ACCURACY:.4} ({matches}/{} match)",
preds_i64.len()
);
let proba = fitted
.predict_proba(&x)
.expect("LogisticRegression predict_proba");
let expected_proba = json_to_array2(&fx.expected["predicted_proba"]);
assert_close_slice(
proba.as_slice().unwrap(),
expected_proba.as_slice().unwrap(),
rel,
abs,
"LogisticRegression.predict_proba",
);
}