1#![allow(dead_code)]
29
30use crate::{SpecialError, SpecialResult};
31use scirs2_core::ndarray::{Array1, ArrayView1};
32use scirs2_core::numeric::{Float, FromPrimitive};
33use scirs2_core::validation::{check_finite, check_positive};
34use std::fmt::{Debug, Display};
35
36#[allow(dead_code)]
67pub fn boxcox<T>(x: T, lmbda: T) -> SpecialResult<T>
68where
69 T: Float + FromPrimitive + Display + Copy + Debug,
70{
71 check_positive(x, "x")?;
72 check_finite(x, "x value")?;
73 check_finite(lmbda, "lmbda value")?;
74
75 let _zero = T::from_f64(0.0).expect("Operation failed");
76 let one = T::one();
77
78 if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
79 Ok(x.ln())
81 } else {
82 Ok((x.powf(lmbda) - one) / lmbda)
84 }
85}
86
87#[allow(dead_code)]
112pub fn boxcox1p<T>(x: T, lmbda: T) -> SpecialResult<T>
113where
114 T: Float + FromPrimitive + Display + Copy + Debug,
115{
116 check_finite(x, "x value")?;
117 check_finite(lmbda, "lmbda value")?;
118
119 let neg_one = T::from_f64(-1.0).expect("Operation failed");
120 if x <= neg_one {
121 return Err(SpecialError::DomainError(
122 "x must be greater than -1".to_string(),
123 ));
124 }
125
126 let _zero = T::from_f64(0.0).expect("Operation failed");
127 let one = T::one();
128 let one_plus_x = one + x;
129
130 if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
131 Ok(one_plus_x.ln())
133 } else {
134 Ok((one_plus_x.powf(lmbda) - one) / lmbda)
136 }
137}
138
139#[allow(dead_code)]
160pub fn inv_boxcox<T>(y: T, lmbda: T) -> SpecialResult<T>
161where
162 T: Float + FromPrimitive + Display + Copy + Debug,
163{
164 check_finite(y, "y value")?;
165 check_finite(lmbda, "lmbda value")?;
166
167 let zero = T::from_f64(0.0).expect("Operation failed");
168 let one = T::one();
169
170 if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
171 Ok(y.exp())
173 } else {
174 let lambda_y_plus_1 = lmbda * y + one;
176
177 if lambda_y_plus_1 <= zero {
178 return Err(SpecialError::DomainError(
179 "Invalid argument for inverse transformation".to_string(),
180 ));
181 }
182
183 Ok(lambda_y_plus_1.powf(one / lmbda))
184 }
185}
186
187#[allow(dead_code)]
208pub fn inv_boxcox1p<T>(y: T, lmbda: T) -> SpecialResult<T>
209where
210 T: Float + FromPrimitive + Display + Copy + Debug,
211{
212 check_finite(y, "y value")?;
213 check_finite(lmbda, "lmbda value")?;
214
215 let zero = T::from_f64(0.0).expect("Operation failed");
216 let one = T::one();
217
218 if lmbda.abs() < T::from_f64(1e-10).expect("Operation failed") {
219 Ok(y.exp() - one)
221 } else {
222 let lambda_y_plus_1 = lmbda * y + one;
224
225 if lambda_y_plus_1 <= zero {
226 return Err(SpecialError::DomainError(
227 "Invalid argument for inverse transformation".to_string(),
228 ));
229 }
230
231 Ok(lambda_y_plus_1.powf(one / lmbda) - one)
232 }
233}
234
235#[allow(dead_code)]
253pub fn boxcox_array<T>(x: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
254where
255 T: Float + FromPrimitive + Display + Copy + Debug,
256{
257 let mut result = Array1::zeros(x.len());
258
259 for (i, &val) in x.iter().enumerate() {
260 result[i] = boxcox(val, lmbda)?;
261 }
262
263 Ok(result)
264}
265
266#[allow(dead_code)]
268pub fn boxcox1p_array<T>(x: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
269where
270 T: Float + FromPrimitive + Display + Copy + Debug,
271{
272 let mut result = Array1::zeros(x.len());
273
274 for (i, &val) in x.iter().enumerate() {
275 result[i] = boxcox1p(val, lmbda)?;
276 }
277
278 Ok(result)
279}
280
281#[allow(dead_code)]
283pub fn inv_boxcox_array<T>(y: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
284where
285 T: Float + FromPrimitive + Display + Copy + Debug,
286{
287 let mut result = Array1::zeros(y.len());
288
289 for (i, &val) in y.iter().enumerate() {
290 result[i] = inv_boxcox(val, lmbda)?;
291 }
292
293 Ok(result)
294}
295
296#[allow(dead_code)]
298pub fn inv_boxcox1p_array<T>(y: &ArrayView1<T>, lmbda: T) -> SpecialResult<Array1<T>>
299where
300 T: Float + FromPrimitive + Display + Copy + Debug,
301{
302 let mut result = Array1::zeros(y.len());
303
304 for (i, &val) in y.iter().enumerate() {
305 result[i] = inv_boxcox1p(val, lmbda)?;
306 }
307
308 Ok(result)
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use approx::assert_relative_eq;
315 use scirs2_core::ndarray::array;
316
317 #[test]
318 fn test_boxcox_basic() {
319 let result = boxcox(std::f64::consts::E, 0.0).expect("Operation failed");
321 assert_relative_eq!(result, 1.0, epsilon = 1e-10);
322
323 let result = boxcox(5.0, 1.0).expect("Operation failed");
325 assert_relative_eq!(result, 4.0, epsilon = 1e-10);
326
327 let result = boxcox(4.0, 0.5).expect("Operation failed");
329 assert_relative_eq!(result, 2.0, epsilon = 1e-10);
330 }
331
332 #[test]
333 fn test_boxcox1p_basic() {
334 let result = boxcox1p(1.718, 0.0).expect("Operation failed");
336 let expected = (1.0 + 1.718_f64).ln();
337 assert_relative_eq!(result, expected, epsilon = 1e-10);
338
339 let result = boxcox1p(0.01, 0.5).expect("Operation failed");
341 assert!(result.is_finite());
342 }
343
344 #[test]
345 fn test_inverse_properties() {
346 let test_values = [0.1, 1.0, 2.0, 5.0, 10.0];
347 let lambdas = [-0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 2.0];
348
349 for &x in &test_values {
350 for &lmbda in &lambdas {
351 let y = boxcox(x, lmbda).expect("Operation failed");
353 let x_recovered = inv_boxcox(y, lmbda).expect("Operation failed");
354 assert_relative_eq!(x, x_recovered, epsilon = 1e-10);
355
356 let y1p = boxcox1p(x, lmbda).expect("Operation failed");
358 let x1p_recovered = inv_boxcox1p(y1p, lmbda).expect("Operation failed");
359 assert_relative_eq!(x, x1p_recovered, epsilon = 1e-10);
360 }
361 }
362 }
363
364 #[test]
365 fn test_array_operations() {
366 let x = array![1.0, 2.0, 4.0, 8.0];
367 let lmbda = 0.5;
368
369 let result = boxcox_array(&x.view(), lmbda).expect("Operation failed");
370 assert_eq!(result.len(), 4);
371
372 let recovered = inv_boxcox_array(&result.view(), lmbda).expect("Operation failed");
374 for i in 0..4 {
375 assert_relative_eq!(x[i], recovered[i], epsilon = 1e-10);
376 }
377 }
378
379 #[test]
380 fn test_error_conditions() {
381 assert!(boxcox(-1.0, 0.5).is_err());
383
384 assert!(boxcox1p(-1.0, 0.5).is_err());
386 assert!(boxcox1p(-1.1, 0.5).is_err());
387 }
388
389 #[test]
390 fn test_continuity_at_lambda_zero() {
391 let x = 2.0;
392 let small_lambda = 1e-12;
393
394 let result_zero = boxcox(x, 0.0).expect("Operation failed");
395 let result_small = boxcox(x, small_lambda).expect("Operation failed");
396
397 assert!((result_zero - result_small).abs() < 1e-6);
399 }
400}