#![allow(dead_code)]
use crate::{SpecialError, SpecialResult};
use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::validation::{check_finite, check_positive};
use std::fmt::{Debug, Display};
#[allow(dead_code)]
pub fn boxcox<T>(x: T, lmbda: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_positive(x, "x")?;
check_finite(x, "x value")?;
check_finite(lmbda, "lmbda value")?;
let _zero = T::from_f64(0.0).expect("Operation failed");
let one = T::one();
if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
Ok(x.ln())
} else {
Ok((x.powf(lmbda) - one) / lmbda)
}
}
#[allow(dead_code)]
pub fn boxcox1p<T>(x: T, lmbda: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_finite(x, "x value")?;
check_finite(lmbda, "lmbda value")?;
let neg_one = T::from_f64(-1.0).expect("Operation failed");
if x <= neg_one {
return Err(SpecialError::DomainError(
"x must be greater than -1".to_string(),
));
}
let _zero = T::from_f64(0.0).expect("Operation failed");
let one = T::one();
let one_plus_x = one + x;
if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
Ok(one_plus_x.ln())
} else {
Ok((one_plus_x.powf(lmbda) - one) / lmbda)
}
}
#[allow(dead_code)]
pub fn inv_boxcox<T>(y: T, lmbda: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_finite(y, "y value")?;
check_finite(lmbda, "lmbda value")?;
let zero = T::from_f64(0.0).expect("Operation failed");
let one = T::one();
if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
Ok(y.exp())
} else {
let lambda_y_plus_1 = lmbda * y + one;
if lambda_y_plus_1 <= zero {
return Err(SpecialError::DomainError(
"Invalid argument for inverse transformation".to_string(),
));
}
Ok(lambda_y_plus_1.powf(one / lmbda))
}
}
#[allow(dead_code)]
pub fn inv_boxcox1p<T>(y: T, lmbda: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_finite(y, "y value")?;
check_finite(lmbda, "lmbda value")?;
let zero = T::from_f64(0.0).expect("Operation failed");
let one = T::one();
if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
Ok(y.exp() - one)
} else {
let lambda_y_plus_1 = lmbda * y + one;
if lambda_y_plus_1 <= zero {
return Err(SpecialError::DomainError(
"Invalid argument for inverse transformation".to_string(),
));
}
Ok(lambda_y_plus_1.powf(one / lmbda) - one)
}
}
#[allow(dead_code)]
pub fn boxcox_array<T>(x: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(x.len());
for (i, &val) in x.iter().enumerate() {
result[i] = boxcox(val, lmbda)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn boxcox1p_array<T>(x: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(x.len());
for (i, &val) in x.iter().enumerate() {
result[i] = boxcox1p(val, lmbda)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn inv_boxcox_array<T>(y: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(y.len());
for (i, &val) in y.iter().enumerate() {
result[i] = inv_boxcox(val, lmbda)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn inv_boxcox1p_array<T>(y: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(y.len());
for (i, &val) in y.iter().enumerate() {
result[i] = inv_boxcox1p(val, lmbda)?;
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_boxcox_basic() {
let result = boxcox(std::f64::consts::E, 0.0).expect("Operation failed");
assert_relative_eq!(result, 1.0, epsilon = 1e-10);
let result = boxcox(5.0, 1.0).expect("Operation failed");
assert_relative_eq!(result, 4.0, epsilon = 1e-10);
let result = boxcox(4.0, 0.5).expect("Operation failed");
assert_relative_eq!(result, 2.0, epsilon = 1e-10);
}
#[test]
fn test_boxcox1p_basic() {
let result = boxcox1p(1.718, 0.0).expect("Operation failed");
let expected = (1.0 + 1.718_f64).ln();
assert_relative_eq!(result, expected, epsilon = 1e-10);
let result = boxcox1p(0.01, 0.5).expect("Operation failed");
assert!(result.is_finite());
}
#[test]
fn test_inverse_properties() {
let test_values = [0.1, 1.0, 2.0, 5.0, 10.0];
let lambdas = [-0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 2.0];
for &x in &test_values {
for &lmbda in &lambdas {
let y = boxcox(x, lmbda).expect("Operation failed");
let x_recovered = inv_boxcox(y, lmbda).expect("Operation failed");
assert_relative_eq!(x, x_recovered, epsilon = 1e-10);
let y1p = boxcox1p(x, lmbda).expect("Operation failed");
let x1p_recovered = inv_boxcox1p(y1p, lmbda).expect("Operation failed");
assert_relative_eq!(x, x1p_recovered, epsilon = 1e-10);
}
}
}
#[test]
fn test_array_operations() {
let x = array![1.0, 2.0, 4.0, 8.0];
let lmbda = 0.5;
let result = boxcox_array(&x.view(), lmbda).expect("Operation failed");
assert_eq!(result.len(), 4);
let recovered = inv_boxcox_array(&result.view(), lmbda).expect("Operation failed");
for i in 0..4 {
assert_relative_eq!(x[i], recovered[i], epsilon = 1e-10);
}
}
#[test]
fn test_error_conditions() {
assert!(boxcox(-1.0, 0.5).is_err());
assert!(boxcox1p(-1.0, 0.5).is_err());
assert!(boxcox1p(-1.1, 0.5).is_err());
}
#[test]
fn test_continuity_at_lambda_zero() {
let x = 2.0;
let small_lambda = 1e-12;
let result_zero = boxcox(x, 0.0).expect("Operation failed");
let result_small = boxcox(x, small_lambda).expect("Operation failed");
assert!((result_zero - result_small).abs() < 1e-6);
}
}