use super::RegularizationType;
use crate::error::Error;
use ndarray::{ArrayBase, Data, Ix1, Ix2};
pub(super) fn preliminary_check<S>(
x: &ArrayBase<S, Ix2>,
y: Option<&ArrayBase<S, Ix1>>,
) -> Result<(), Error>
where
S: Data<Elem = f64>,
{
if x.nrows() == 0 {
return Err(Error::empty_input("input data"));
}
for (i, row) in x.outer_iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
if val.is_nan() || val.is_infinite() {
return Err(Error::non_finite(format!(
"input data at position [{}][{}]",
i, j
)));
}
}
}
if let Some(y) = y {
if y.is_empty() {
return Err(Error::empty_input("target vector"));
}
if y.len() != x.nrows() {
return Err(Error::dimension_mismatch(x.nrows(), y.len()));
}
}
Ok(())
}
pub(super) fn validate_learning_rate(learning_rate: f64) -> Result<(), Error> {
if learning_rate <= 0.0 || !learning_rate.is_finite() {
return Err(Error::invalid_parameter(
"learning_rate",
format!("must be positive and finite, got {}", learning_rate),
));
}
Ok(())
}
pub(super) fn validate_max_iterations(max_iterations: usize) -> Result<(), Error> {
if max_iterations == 0 {
return Err(Error::invalid_parameter(
"max_iterations",
"must be greater than 0",
));
}
Ok(())
}
pub(super) fn validate_tolerance(tolerance: f64) -> Result<(), Error> {
if tolerance <= 0.0 || !tolerance.is_finite() {
return Err(Error::invalid_parameter(
"tolerance",
format!("must be positive and finite, got {}", tolerance),
));
}
Ok(())
}
pub(super) fn validate_regularization_type(
reg_type: Option<RegularizationType>,
) -> Result<(), Error> {
if let Some(reg) = ®_type {
match reg {
RegularizationType::L1(alpha) | RegularizationType::L2(alpha) => {
if *alpha < 0.0 || !alpha.is_finite() {
return Err(Error::invalid_parameter(
"alpha",
format!("must be non-negative and finite, got {}", alpha),
));
}
}
}
}
Ok(())
}
#[inline]
pub(super) fn check_is_fitted(
is_fitted: bool,
model: &'static str,
) -> crate::error::RustymlResult<()> {
if is_fitted {
Ok(())
} else {
Err(Error::not_fitted(model))
}
}
pub(super) fn validate_predict_input<S>(
x: &ArrayBase<S, Ix2>,
expected_features: usize,
) -> Result<(), Error>
where
S: Data<Elem = f64>,
{
if x.is_empty() {
return Err(Error::empty_input("dataset to predict on"));
}
if x.ncols() != expected_features {
return Err(Error::dimension_mismatch(expected_features, x.ncols()));
}
if x.iter().any(|&val| !val.is_finite()) {
return Err(Error::non_finite("input data"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
use ndarray::{Array1, Array2};
#[test]
fn preliminary_check_empty_x_gives_empty_input() {
let x: Array2<f64> = Array2::zeros((0, 3));
let err = preliminary_check(&x, None).unwrap_err();
match err {
Error::EmptyInput(_) => {}
other => panic!("expected EmptyInput, got {:?}", other),
}
}
#[test]
fn preliminary_check_x_with_nan_gives_non_finite() {
let x = ndarray::array![[1.0, f64::NAN], [2.0, 3.0]];
let err = preliminary_check(&x, None).unwrap_err();
match err {
Error::NonFinite(_) => {}
other => panic!("expected NonFinite, got {:?}", other),
}
}
#[test]
fn preliminary_check_x_with_inf_gives_non_finite() {
let x = ndarray::array![[1.0, f64::INFINITY]];
let err = preliminary_check(&x, None).unwrap_err();
match err {
Error::NonFinite(_) => {}
other => panic!("expected NonFinite, got {:?}", other),
}
}
#[test]
fn preliminary_check_y_len_mismatch_gives_dimension_mismatch() {
let x = ndarray::array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
let y = Array1::from_vec(vec![1.0, 2.0]); let err = preliminary_check(&x, Some(&y)).unwrap_err();
match err {
Error::DimensionMismatch { expected, found } => {
assert_eq!(expected, 3, "expected count should be x.nrows()=3");
assert_eq!(found, 2, "found count should be y.len()=2");
}
other => panic!("expected DimensionMismatch, got {:?}", other),
}
}
#[test]
fn preliminary_check_valid_x_no_y_gives_ok() {
let x = ndarray::array![[1.0, 2.0], [3.0, 4.0]];
assert!(preliminary_check(&x, None).is_ok());
}
#[test]
fn preliminary_check_valid_x_and_y_gives_ok() {
let x = ndarray::array![[1.0, 2.0], [3.0, 4.0]];
let y = Array1::from_vec(vec![0.0, 1.0]);
assert!(preliminary_check(&x, Some(&y)).is_ok());
}
#[test]
fn validate_predict_input_empty_gives_empty_input() {
let x: Array2<f64> = Array2::zeros((0, 3));
let err = validate_predict_input(&x, 3).unwrap_err();
match err {
Error::EmptyInput(_) => {}
other => panic!("expected EmptyInput, got {:?}", other),
}
}
#[test]
fn validate_predict_input_ncols_mismatch_gives_dimension_mismatch() {
let x = ndarray::array![[1.0, 2.0], [3.0, 4.0]]; let err = validate_predict_input(&x, 3).unwrap_err(); match err {
Error::DimensionMismatch { expected, found } => {
assert_eq!(expected, 3);
assert_eq!(found, 2);
}
other => panic!("expected DimensionMismatch, got {:?}", other),
}
}
#[test]
fn validate_predict_input_nan_gives_non_finite() {
let x = ndarray::array![[1.0, f64::NAN], [2.0, 3.0]];
let err = validate_predict_input(&x, 2).unwrap_err();
match err {
Error::NonFinite(_) => {}
other => panic!("expected NonFinite, got {:?}", other),
}
}
#[test]
fn validate_predict_input_valid_gives_ok() {
let x = ndarray::array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
assert!(validate_predict_input(&x, 3).is_ok());
}
#[test]
fn validate_learning_rate_positive_gives_ok() {
assert!(validate_learning_rate(0.1).is_ok());
}
#[test]
fn validate_learning_rate_zero_gives_invalid_parameter() {
let err = validate_learning_rate(0.0).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => {
assert_eq!(name, "learning_rate");
}
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_learning_rate_negative_gives_invalid_parameter() {
let err = validate_learning_rate(-1.0).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "learning_rate"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_learning_rate_nan_gives_invalid_parameter() {
let err = validate_learning_rate(f64::NAN).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "learning_rate"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_learning_rate_inf_gives_invalid_parameter() {
let err = validate_learning_rate(f64::INFINITY).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "learning_rate"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_max_iterations_one_gives_ok() {
assert!(validate_max_iterations(1).is_ok());
}
#[test]
fn validate_max_iterations_zero_gives_invalid_parameter() {
let err = validate_max_iterations(0).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => {
assert_eq!(name, "max_iterations");
}
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_tolerance_positive_gives_ok() {
assert!(validate_tolerance(1e-4).is_ok());
}
#[test]
fn validate_tolerance_zero_gives_invalid_parameter() {
let err = validate_tolerance(0.0).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "tolerance"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_tolerance_negative_gives_invalid_parameter() {
let err = validate_tolerance(-0.5).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "tolerance"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_tolerance_nan_gives_invalid_parameter() {
let err = validate_tolerance(f64::NAN).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "tolerance"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_tolerance_inf_gives_invalid_parameter() {
let err = validate_tolerance(f64::INFINITY).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "tolerance"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_regularization_type_none_gives_ok() {
assert!(validate_regularization_type(None).is_ok());
}
#[test]
fn validate_regularization_type_l1_zero_gives_ok() {
assert!(validate_regularization_type(Some(RegularizationType::L1(0.0))).is_ok());
}
#[test]
fn validate_regularization_type_l2_positive_gives_ok() {
assert!(validate_regularization_type(Some(RegularizationType::L2(0.5))).is_ok());
}
#[test]
fn validate_regularization_type_l1_negative_gives_invalid_parameter() {
let err = validate_regularization_type(Some(RegularizationType::L1(-1.0))).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "alpha"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_regularization_type_l1_nan_gives_invalid_parameter() {
let err = validate_regularization_type(Some(RegularizationType::L1(f64::NAN))).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "alpha"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn validate_regularization_type_l1_inf_gives_invalid_parameter() {
let err =
validate_regularization_type(Some(RegularizationType::L1(f64::INFINITY))).unwrap_err();
match err {
Error::InvalidParameter { name, .. } => assert_eq!(name, "alpha"),
other => panic!("expected InvalidParameter, got {:?}", other),
}
}
#[test]
fn check_is_fitted_true_gives_ok() {
assert!(check_is_fitted(true, "SomeModel").is_ok());
}
#[test]
fn check_is_fitted_false_gives_not_fitted() {
let err = check_is_fitted(false, "SomeModel").unwrap_err();
match err {
Error::NotFitted(model) => assert_eq!(model, "SomeModel"),
other => panic!("expected NotFitted, got {:?}", other),
}
}
}