#[cfg(not(feature = "std"))]
use alloc::format;
use num_traits::Float;
use crate::primitives::errors::LoessError;
pub struct Validator;
impl Validator {
pub fn validate_inputs<T: Float>(
x: &[T],
y: &[T],
dimensions: usize,
) -> Result<(), LoessError> {
if x.is_empty() || y.is_empty() {
return Err(LoessError::EmptyInput);
}
let n_y = y.len();
if x.len() != n_y * dimensions {
return Err(LoessError::MismatchedInputs {
x_len: x.len(),
y_len: n_y,
});
}
if n_y < 2 {
return Err(LoessError::TooFewPoints { got: n_y, min: 2 });
}
for (i, &val) in x.iter().enumerate() {
if !val.is_finite() {
return Err(LoessError::InvalidNumericValue(format!(
"x[{}]={}",
i,
val.to_f64().unwrap_or(f64::NAN)
)));
}
}
for (i, &val) in y.iter().enumerate() {
if !val.is_finite() {
return Err(LoessError::InvalidNumericValue(format!(
"y[{}]={}",
i,
val.to_f64().unwrap_or(f64::NAN)
)));
}
}
Ok(())
}
pub fn validate_scalar<T: Float>(val: T, name: &str) -> Result<(), LoessError> {
if !val.is_finite() {
return Err(LoessError::InvalidNumericValue(format!(
"{}={}",
name,
val.to_f64().unwrap_or(f64::NAN)
)));
}
Ok(())
}
pub fn validate_fraction<T: Float>(fraction: T) -> Result<(), LoessError> {
if !fraction.is_finite() || fraction <= T::zero() || fraction > T::one() {
return Err(LoessError::InvalidFraction(
fraction.to_f64().unwrap_or(f64::NAN),
));
}
Ok(())
}
pub fn validate_iterations(iterations: usize) -> Result<(), LoessError> {
const MAX_ITERATIONS: usize = 1000;
if iterations > MAX_ITERATIONS {
return Err(LoessError::InvalidIterations(iterations));
}
Ok(())
}
pub fn validate_interval_level<T: Float>(level: T) -> Result<(), LoessError> {
if !level.is_finite() || level <= T::zero() || level >= T::one() {
return Err(LoessError::InvalidIntervals(
level.to_f64().unwrap_or(f64::NAN),
));
}
Ok(())
}
pub fn validate_cv_fractions<T: Float>(fracs: &[T]) -> Result<(), LoessError> {
if fracs.is_empty() {
return Err(LoessError::InvalidFraction(0.0));
}
for &f in fracs {
Self::validate_fraction(f)?;
}
Ok(())
}
pub fn validate_kfold(k: usize) -> Result<(), LoessError> {
if k < 2 {
return Err(LoessError::InvalidNumericValue(format!(
"k-fold must be at least 2, got {}",
k
)));
}
Ok(())
}
pub fn validate_tolerance<T: Float>(tol: T) -> Result<(), LoessError> {
if !tol.is_finite() || tol <= T::zero() {
return Err(LoessError::InvalidTolerance(
tol.to_f64().unwrap_or(f64::NAN),
));
}
Ok(())
}
pub fn validate_interpolation_grid<T: Float>(
cell: T,
fraction: T,
dimensions: usize,
max_vertices: usize,
cell_provided: bool,
limit_provided: bool,
) -> Result<(), LoessError> {
let cell_f64 = cell.to_f64().unwrap_or(0.2);
if cell_f64 <= 0.0 || cell_f64 > 1.0 {
return Err(LoessError::InvalidCell(cell_f64));
}
if cell_provided && limit_provided {
let one = T::one();
let width_fraction = fraction * cell;
if width_fraction <= T::zero() {
return Err(LoessError::InsufficientVertices {
required: usize::MAX,
limit: max_vertices,
cell: cell_f64,
cell_provided,
limit_provided,
});
}
let cells_per_dim = one / width_fraction;
let cells_per_dim_f64 = cells_per_dim.to_f64().unwrap_or(f64::INFINITY);
let vertices_per_dim = cells_per_dim_f64.ceil() + 1.0;
let estimated_vertices = vertices_per_dim.powi(dimensions as i32);
if estimated_vertices > max_vertices as f64 {
let required = if estimated_vertices > (usize::MAX as f64) {
usize::MAX
} else {
estimated_vertices as usize
};
return Err(LoessError::InsufficientVertices {
required,
limit: max_vertices,
cell: cell_f64,
cell_provided,
limit_provided,
});
}
}
Ok(())
}
pub fn validate_chunk_size(chunk_size: usize, min: usize) -> Result<(), LoessError> {
if chunk_size < min {
return Err(LoessError::InvalidChunkSize {
got: chunk_size,
min,
});
}
Ok(())
}
pub fn validate_overlap(overlap: usize, chunk_size: usize) -> Result<(), LoessError> {
if overlap >= chunk_size {
return Err(LoessError::InvalidOverlap {
overlap,
chunk_size,
});
}
Ok(())
}
pub fn validate_window_capacity(window_capacity: usize, min: usize) -> Result<(), LoessError> {
if window_capacity < min {
return Err(LoessError::InvalidWindowCapacity {
got: window_capacity,
min,
});
}
Ok(())
}
pub fn validate_min_points(
min_points: usize,
window_capacity: usize,
) -> Result<(), LoessError> {
if min_points < 2 || min_points > window_capacity {
return Err(LoessError::InvalidMinPoints {
got: min_points,
window_capacity,
});
}
Ok(())
}
pub fn validate_no_duplicates(duplicate_param: Option<&'static str>) -> Result<(), LoessError> {
if let Some(param) = duplicate_param {
return Err(LoessError::DuplicateParameter { parameter: param });
}
Ok(())
}
}