///|
/// Root mean square of finite signal values.
pub fn root_mean_square(values : Array[Double]) -> Double {
let mut sum = 0.0
let mut count = 0
for value in values {
if !value.is_nan() && !value.is_inf() {
sum = sum + value * value
count = count + 1
}
}
if count == 0 {
0.0
} else {
(sum / count.to_double()).sqrt()
}
}
///|
/// Coefficient of variation, expressed as a unitless ratio.
pub fn coefficient_of_variation(values : Array[Double]) -> Double {
let mean = compute_mean(values)
if mean.abs() < 0.000001 {
0.0
} else {
compute_std_dev(values) / mean.abs()
}
}
///|
/// Causal rolling mean with a shrinking prefix window.
pub fn rolling_mean(values : Array[Double], window_size : Int) -> Array[Double] {
let n = values.length()
if n == 0 {
return []
}
let window = if window_size <= 1 { 1 } else { window_size }
let result : Array[Double] = Array::new(capacity=n)
for i = 0; i < n; i = i + 1 {
let start = if i + 1 > window { i + 1 - window } else { 0 }
result.push(mean_in_range(values, start, i + 1))
}
result
}
///|
/// Normalized autocorrelation at a non-negative integer lag.
pub fn autocorrelation(values : Array[Double], lag : Int) -> Double {
let n = values.length()
if lag < 0 || lag >= n {
return 0.0
}
let mean = compute_mean(values)
let mut denominator = 0.0
let mut numerator = 0.0
for i = 0; i < n; i = i + 1 {
let centered = values[i] - mean
denominator = denominator + centered * centered
if i + lag < n {
numerator = numerator + centered * (values[i + lag] - mean)
}
}
if denominator < 0.000001 {
if lag == 0 {
1.0
} else {
0.0
}
} else {
numerator / denominator
}
}
///|
/// Linearly maps finite values to [0, 1]. Constant signals map to zero.
pub fn normalize_minmax(values : Array[Double]) -> Array[Double] {
if values.length() == 0 {
return []
}
let finite : Array[Double] = []
for value in values {
if !value.is_nan() && !value.is_inf() {
finite.push(value)
}
}
if finite.length() == 0 {
let zeros : Array[Double] = []
for _ in values {
zeros.push(0.0)
}
return zeros
}
let min_value = finite[0]
let max_value = finite[0]
let mut lo = min_value
let mut hi = max_value
for value in finite {
if value < lo {
lo = value
}
if value > hi {
hi = value
}
}
let span = hi - lo
let result : Array[Double] = []
if span.abs() < 0.000001 {
for _ in values {
result.push(0.0)
}
} else {
for value in values {
if value.is_nan() || value.is_inf() {
result.push(0.0)
} else {
result.push((value - lo) / span)
}
}
}
result
}
///|
/// Converts a finite signal into z-scores; degenerate signals map to zero.
pub fn zscore(values : Array[Double]) -> Array[Double] {
let mean = compute_mean(values)
let sd = compute_std_dev(values)
let result : Array[Double] = []
for value in values {
if sd < 0.000001 || value.is_nan() || value.is_inf() {
result.push(0.0)
} else {
result.push((value - mean) / sd)
}
}
result
}