pub fn difference(series: &[f64], d: usize) -> Vec<f64> {
if d == 0 || series.is_empty() {
return series.to_vec();
}
let mut result = series.to_vec();
for _ in 0..d {
if result.len() <= 1 {
break;
}
result = result.windows(2).map(|w| w[1] - w[0]).collect();
}
result
}
pub fn seasonal_difference(series: &[f64], d: usize, period: usize) -> Vec<f64> {
if d == 0 || period == 0 || series.len() <= period {
return series.to_vec();
}
let mut result = series.to_vec();
for _ in 0..d {
if result.len() <= period {
break;
}
result = result
.iter()
.skip(period)
.zip(result.iter())
.map(|(curr, prev)| curr - prev)
.collect();
}
result
}
pub fn integrate(differenced: &[f64], original: &[f64], d: usize) -> Vec<f64> {
if d == 0 || differenced.is_empty() {
return differenced.to_vec();
}
let mut result = differenced.to_vec();
for level in (0..d).rev() {
let init_value = if level == 0 {
*original.last().unwrap_or(&0.0)
} else {
let intermediate = difference(original, level);
*intermediate.last().unwrap_or(&0.0)
};
let mut integrated = Vec::with_capacity(result.len());
let mut cumsum = init_value;
for &diff in &result {
cumsum += diff;
integrated.push(cumsum);
}
result = integrated;
}
result
}
pub fn seasonal_integrate(
differenced: &[f64],
original: &[f64],
d: usize,
period: usize,
) -> Vec<f64> {
if d == 0 || period == 0 || differenced.is_empty() {
return differenced.to_vec();
}
let mut result = differenced.to_vec();
for level in (0..d).rev() {
let reference = if level == 0 {
original.to_vec()
} else {
seasonal_difference(original, level, period)
};
let mut integrated = Vec::with_capacity(result.len());
let ref_len = reference.len();
for (i, &diff_val) in result.iter().enumerate() {
let prev = if i < period {
let ref_idx = ref_len.wrapping_sub(period).wrapping_add(i);
if ref_idx < ref_len {
reference[ref_idx]
} else {
0.0
}
} else {
integrated[i - period]
};
integrated.push(diff_val + prev);
}
result = integrated;
}
result
}
pub fn suggest_differencing(series: &[f64]) -> usize {
if series.len() < 3 {
return 0;
}
let var_0 = variance(series);
let diff_1 = difference(series, 1);
if !variance_decreased_significantly(var_0, &diff_1) {
return 0;
}
let var_1 = variance(&diff_1);
let diff_2 = difference(&diff_1, 1);
if diff_2.len() >= 2 {
let var_2 = variance(&diff_2);
if var_2 / var_1 < 0.9 && var_2 < var_0 {
return 2;
}
}
1
}
#[inline]
fn variance_decreased_significantly(var_original: f64, differenced: &[f64]) -> bool {
if differenced.len() < 2 || var_original <= 0.0 {
return false;
}
variance(differenced) / var_original < 0.9
}
fn variance(series: &[f64]) -> f64 {
if series.len() < 2 {
return 0.0;
}
crate::simd::variance_sample(series)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn difference_order_0() {
let series = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = difference(&series, 0);
assert_eq!(result, series);
}
#[test]
fn difference_order_1() {
let series = vec![1.0, 3.0, 6.0, 10.0, 15.0];
let result = difference(&series, 1);
assert_eq!(result, vec![2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn difference_order_2() {
let series = vec![1.0, 3.0, 6.0, 10.0, 15.0];
let result = difference(&series, 2);
assert_eq!(result, vec![1.0, 1.0, 1.0]);
}
#[test]
fn difference_constant_series() {
let series = vec![5.0, 5.0, 5.0, 5.0];
let result = difference(&series, 1);
assert_eq!(result, vec![0.0, 0.0, 0.0]);
}
#[test]
fn difference_empty() {
let series: Vec<f64> = vec![];
let result = difference(&series, 1);
assert!(result.is_empty());
}
#[test]
fn seasonal_difference_basic() {
let series = vec![
100.0, 120.0, 80.0, 90.0, 110.0, 130.0, 90.0, 100.0, ];
let result = seasonal_difference(&series, 1, 4);
assert_eq!(result, vec![10.0, 10.0, 10.0, 10.0]);
}
#[test]
fn seasonal_difference_no_change() {
let series = vec![1.0, 2.0, 3.0, 1.0, 2.0, 3.0];
let result = seasonal_difference(&series, 1, 3);
assert_eq!(result, vec![0.0, 0.0, 0.0]);
}
#[test]
fn seasonal_difference_order_0() {
let series = vec![1.0, 2.0, 3.0, 4.0];
let result = seasonal_difference(&series, 0, 2);
assert_eq!(result, series);
}
#[test]
fn integrate_reverses_difference() {
let original = vec![10.0, 12.0, 15.0, 19.0, 24.0];
let _differenced = difference(&original, 1);
let forecast_diff = vec![6.0, 7.0]; let integrated = integrate(&forecast_diff, &original, 1);
assert_relative_eq!(integrated[0], 30.0, epsilon = 1e-10);
assert_relative_eq!(integrated[1], 37.0, epsilon = 1e-10);
}
#[test]
fn integrate_order_2() {
let original = vec![1.0, 3.0, 6.0, 10.0, 15.0];
let _differenced = difference(&original, 2);
let forecast_diff2 = vec![1.0, 1.0]; let integrated = integrate(&forecast_diff2, &original, 2);
assert!(integrated.len() == 2);
}
#[test]
fn suggest_differencing_stationary() {
let series = vec![1.0, 0.5, 1.2, 0.8, 1.1, 0.9, 1.0, 1.1];
let d = suggest_differencing(&series);
assert_eq!(d, 0);
}
#[test]
fn suggest_differencing_trend() {
let series: Vec<f64> = (0..20).map(|i| 10.0 + 2.0 * i as f64).collect();
let d = suggest_differencing(&series);
assert!(d >= 1);
}
#[test]
fn suggest_differencing_quadratic() {
let series: Vec<f64> = (0..20).map(|i| (i * i) as f64).collect();
let d = suggest_differencing(&series);
assert!(d >= 1);
}
#[test]
fn seasonal_integrate_reverses_seasonal_difference() {
let original = vec![
100.0, 120.0, 80.0, 90.0, 110.0, 130.0, 90.0, 100.0, ];
let diffed = seasonal_difference(&original, 1, 4);
assert_eq!(diffed, vec![10.0, 10.0, 10.0, 10.0]);
let forecast_diff = vec![10.0, 10.0, 10.0, 10.0];
let integrated = seasonal_integrate(&forecast_diff, &original, 1, 4);
assert_relative_eq!(integrated[0], 120.0, epsilon = 1e-10);
assert_relative_eq!(integrated[1], 140.0, epsilon = 1e-10);
assert_relative_eq!(integrated[2], 100.0, epsilon = 1e-10);
assert_relative_eq!(integrated[3], 110.0, epsilon = 1e-10);
}
#[test]
fn seasonal_integrate_order_0_is_identity() {
let forecast = vec![1.0, 2.0, 3.0];
let original = vec![10.0, 20.0, 30.0, 40.0];
let result = seasonal_integrate(&forecast, &original, 0, 4);
assert_eq!(result, forecast);
}
#[test]
fn seasonal_integrate_empty() {
let result = seasonal_integrate(&[], &[1.0, 2.0], 1, 2);
assert!(result.is_empty());
}
#[test]
fn seasonal_integrate_reverses_multi_period_forecast() {
let period = 7;
let weekly_base = [10.0, 20.0, 15.0, 25.0, 30.0, 12.0, 8.0];
let growth = 5.0;
let original: Vec<f64> = (0..21)
.map(|i| weekly_base[i % period] + growth * (i / period) as f64)
.collect();
let diffed = seasonal_difference(&original, 1, period);
assert_eq!(diffed.len(), 14);
for &d in &diffed {
assert_relative_eq!(d, growth, epsilon = 1e-10);
}
let forecast_diff: Vec<f64> = vec![growth; 14];
let integrated = seasonal_integrate(&forecast_diff, &original, 1, period);
for i in 0..7 {
let expected = weekly_base[i] + growth * 3.0;
assert_relative_eq!(integrated[i], expected, epsilon = 1e-10);
}
for i in 0..7 {
let expected = weekly_base[i] + growth * 4.0;
assert_relative_eq!(integrated[7 + i], expected, epsilon = 1e-10);
}
}
#[test]
fn seasonal_integrate_horizon_less_than_period() {
let period = 7;
let original = vec![
10.0, 20.0, 15.0, 25.0, 30.0, 12.0, 8.0, 15.0, 25.0, 20.0, 30.0, 35.0, 17.0, 13.0, ];
let forecast_diff = vec![5.0, 5.0, 5.0];
let integrated = seasonal_integrate(&forecast_diff, &original, 1, period);
assert_eq!(integrated.len(), 3);
assert_relative_eq!(integrated[0], 20.0, epsilon = 1e-10);
assert_relative_eq!(integrated[1], 30.0, epsilon = 1e-10);
assert_relative_eq!(integrated[2], 25.0, epsilon = 1e-10);
}
#[test]
fn seasonal_integrate_horizon_greater_than_period() {
let period = 4;
let original = vec![
100.0, 200.0, 150.0, 250.0, 110.0, 210.0, 160.0, 260.0, ];
let forecast_diff = vec![10.0; 6];
let integrated = seasonal_integrate(&forecast_diff, &original, 1, period);
assert_eq!(integrated.len(), 6);
assert_relative_eq!(integrated[0], 120.0, epsilon = 1e-10);
assert_relative_eq!(integrated[1], 220.0, epsilon = 1e-10);
assert_relative_eq!(integrated[2], 170.0, epsilon = 1e-10);
assert_relative_eq!(integrated[3], 270.0, epsilon = 1e-10);
assert_relative_eq!(integrated[4], 130.0, epsilon = 1e-10);
assert_relative_eq!(integrated[5], 230.0, epsilon = 1e-10);
}
#[test]
fn seasonal_integrate_d2_reversal() {
let original = vec![
100.0, 120.0, 80.0, 90.0, 110.0, 130.0, 90.0, 100.0, 125.0, 145.0, 105.0, 115.0, ];
let diffed = seasonal_difference(&original, 2, 4);
assert_eq!(diffed.len(), 4);
for &d in &diffed {
assert_relative_eq!(d, 5.0, epsilon = 1e-10);
}
let forecast_diff = vec![5.0; 4];
let integrated = seasonal_integrate(&forecast_diff, &original, 2, 4);
assert_eq!(integrated.len(), 4);
assert_relative_eq!(integrated[0], 145.0, epsilon = 1e-10);
assert_relative_eq!(integrated[1], 165.0, epsilon = 1e-10);
assert_relative_eq!(integrated[2], 125.0, epsilon = 1e-10);
assert_relative_eq!(integrated[3], 135.0, epsilon = 1e-10);
}
#[test]
fn integrate_reverses_difference_d1_multi_step() {
let original = vec![10.0, 13.0, 17.0, 22.0, 28.0];
let forecast_diff = vec![7.0, 8.0, 9.0, 10.0];
let integrated = integrate(&forecast_diff, &original, 1);
assert_relative_eq!(integrated[0], 35.0, epsilon = 1e-10); assert_relative_eq!(integrated[1], 43.0, epsilon = 1e-10); assert_relative_eq!(integrated[2], 52.0, epsilon = 1e-10); assert_relative_eq!(integrated[3], 62.0, epsilon = 1e-10); }
#[test]
fn difference_then_integrate_roundtrip_d1() {
let original = vec![5.0, 8.0, 12.0, 17.0, 23.0, 30.0];
let diffed = difference(&original, 1);
let recovered = integrate(&diffed, &[original[0]], 1);
for (i, &v) in recovered.iter().enumerate() {
assert_relative_eq!(v, original[i + 1], epsilon = 1e-10);
}
}
#[test]
fn seasonal_difference_then_integrate_roundtrip() {
let period = 7;
let original: Vec<f64> = (0..28)
.map(|i| {
let base = [10.0, 20.0, 15.0, 25.0, 30.0, 12.0, 8.0];
base[i % period] + 3.0 * (i / period) as f64
})
.collect();
let diffed = seasonal_difference(&original, 1, period);
assert_eq!(diffed.len(), 21);
for &d in &diffed {
assert_relative_eq!(d, 3.0, epsilon = 1e-10);
}
let forecast_diff = vec![3.0; 7];
let integrated = seasonal_integrate(&forecast_diff, &original, 1, period);
let week4_base = [10.0, 20.0, 15.0, 25.0, 30.0, 12.0, 8.0];
for i in 0..7 {
let expected = week4_base[i] + 3.0 * 4.0; assert_relative_eq!(integrated[i], expected, epsilon = 1e-10);
}
}
}