///|
/// A bounded feature window for streaming sensor preprocessing.
pub struct RollingWindow {
capacity : Int
mut values : Array[Double]
}
///|
pub fn RollingWindow::new(capacity : Int) -> RollingWindow {
{ capacity: if capacity < 1 { 1 } else { capacity }, values: [] }
}
///|
pub fn RollingWindow::push(self : RollingWindow, value : Double) -> Unit {
self.values.push(value)
if self.values.length() > self.capacity {
let trimmed = self.values[self.values.length() - self.capacity:].to_owned()
self.values = trimmed
}
}
///|
pub fn RollingWindow::length(self : RollingWindow) -> Int {
self.values.length()
}
///|
pub fn RollingWindow::capacity(self : RollingWindow) -> Int {
self.capacity
}
///|
pub fn RollingWindow::values(self : RollingWindow) -> Array[Double] {
self.values.copy()
}
///|
pub fn RollingWindow::mean(self : RollingWindow) -> Double {
vector_mean(self.values)
}
///|
pub fn RollingWindow::variance(self : RollingWindow) -> Double {
vector_variance(self.values)
}
///|
pub fn RollingWindow::minimum(self : RollingWindow) -> Double {
if self.values.length() == 0 {
return 0.0
}
let mut result = self.values[0]
for value in self.values {
if value < result {
result = value
}
}
result
}
///|
pub fn RollingWindow::maximum(self : RollingWindow) -> Double {
if self.values.length() == 0 {
return 0.0
}
let mut result = self.values[0]
for value in self.values {
if value > result {
result = value
}
}
result
}
///|
pub fn signal_difference(
values : Array[Double],
spacing : Double,
) -> Array[Double] {
if values.length() == 0 {
return []
}
if values.length() == 1 {
return [0.0]
}
let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
let result = Array::make(values.length(), 0.0)
result[0] = (values[1] - values[0]) / safe_spacing
for i in 1.. Array[Double] {
let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
let result = Array::make(values.length(), 0.0)
for i in 1.. Array[Double] {
if values.length() == 0 {
return []
}
let size = if window < 1 { 1 } else { window }
Array::makei(values.length(), i => {
let start = if i + 1 > size { i + 1 - size } else { 0 }
vector_mean(values[start:i + 1].to_owned())
})
}
///|
pub fn rolling_variance(values : Array[Double], window : Int) -> Array[Double] {
if values.length() == 0 {
return []
}
let size = if window < 1 { 1 } else { window }
Array::makei(values.length(), i => {
let start = if i + 1 > size { i + 1 - size } else { 0 }
vector_variance(values[start:i + 1].to_owned())
})
}
///|
pub fn linear_slope(values : Array[Double], spacing : Double) -> Double {
if values.length() < 2 {
return 0.0
}
let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
let x_mean = (values.length() - 1).to_double() * 0.5
let y_mean = vector_mean(values)
let mut numerator = 0.0
let mut denominator = 0.0
for i, value in values {
let x = i.to_double()
numerator = numerator + (x - x_mean) * (value - y_mean)
denominator = denominator + (x - x_mean) * (x - x_mean)
}
if denominator <= 0.0 {
0.0
} else {
numerator / denominator / safe_spacing
}
}
///|
pub fn signal_correlation(
left : Array[Double],
right : Array[Double],
) -> Double {
if left.length() != right.length() || left.length() < 2 {
return 0.0
}
let centered_left = vector_mean_center(left)
let centered_right = vector_mean_center(right)
let denominator = vector_l2_norm(centered_left) *
vector_l2_norm(centered_right)
if denominator <= 0.000000000001 {
0.0
} else {
vector_dot(centered_left, centered_right) / denominator
}
}
///|
pub fn signal_autocorrelation(values : Array[Double], lag : Int) -> Double {
if lag < 0 || lag >= values.length() {
return 0.0
}
let left = values[lag:].to_owned()
let right = values[:values.length() - lag].to_owned()
signal_correlation(left, right)
}
///|
pub fn detect_spikes(
values : Array[Double],
threshold : Double,
radius : Int,
) -> Array[Bool] {
if values.length() == 0 {
return []
}
let safe_threshold = if threshold < 0.0 { 0.0 } else { threshold }
let safe_radius = if radius < 1 { 1 } else { radius }
Array::makei(values.length(), index => {
let start = if index > safe_radius { index - safe_radius } else { 0 }
let end = if index + safe_radius + 1 > values.length() {
values.length()
} else {
index + safe_radius + 1
}
let window : Array[Double] = []
for i in start.. safe_threshold
}
})
}
///|
pub fn interpolate_missing(
values : Array[Double?],
fallback : Double,
) -> Array[Double] {
let result = Array::make(values.length(), fallback)
let mut last_index = -1
let mut last_value = fallback
for i, value in values {
match value {
Some(current) => {
if last_index < 0 {
for j in 0.. ()
}
}
if last_index >= 0 {
for i in (last_index + 1).. Array[Double] {
if factor <= 0 {
return []
}
let result : Array[Double] = []
for i, value in values {
if i % factor == 0 {
result.push(value)
}
}
result
}
///|
pub fn resample_linear(
values : Array[Double],
output_length : Int,
) -> Array[Double] {
if output_length <= 0 || values.length() == 0 {
return []
}
if values.length() == 1 {
return Array::make(output_length, values[0])
}
if output_length == 1 {
return [values[0]]
}
let result = Array::make(output_length, 0.0)
for i in 0..= values.length() { lower } else { lower + 1 }
let amount = position - lower.to_double()
result[i] = values[lower] + amount * (values[upper] - values[lower])
}
result
}
///|
pub fn normalize_signal(values : Array[Double]) -> Array[Double] {
let mean = vector_mean(values)
let deviation = vector_variance(values).sqrt()
if deviation <= 0.000000000001 {
return values.map(_ => 0.0)
}
values.map(value => (value - mean) / deviation)
}
///|
pub fn hampel_filter(
values : Array[Double],
radius : Int,
threshold : Double,
) -> Array[Double] {
if values.length() == 0 {
return []
}
let safe_radius = if radius < 1 { 1 } else { radius }
let safe_threshold = if threshold <= 0.0 { 3.0 } else { threshold }
Array::makei(values.length(), index => {
let start = if index > safe_radius { index - safe_radius } else { 0 }
let end = if index + safe_radius + 1 > values.length() {
values.length()
} else {
index + safe_radius + 1
}
let window = values[start:end].to_owned()
let median = vector_median(window)
let deviation = vector_mad(window)
if deviation <= 0.000000000001 {
if (values[index] - median).abs() > 0.000000000001 {
median
} else {
values[index]
}
} else if (values[index] - median).abs() <= safe_threshold * deviation {
values[index]
} else {
median
}
})
}
///|
pub struct FeatureVector {
mean : Double
variance : Double
slope : Double
minimum : Double
maximum : Double
energy : Double
} derive(Debug)
///|
pub fn FeatureVector::mean(self : FeatureVector) -> Double {
self.mean
}
///|
pub fn FeatureVector::variance(self : FeatureVector) -> Double {
self.variance
}
///|
pub fn FeatureVector::slope(self : FeatureVector) -> Double {
self.slope
}
///|
pub fn FeatureVector::minimum(self : FeatureVector) -> Double {
self.minimum
}
///|
pub fn FeatureVector::maximum(self : FeatureVector) -> Double {
self.maximum
}
///|
pub fn FeatureVector::energy(self : FeatureVector) -> Double {
self.energy
}
///|
pub fn extract_features(
values : Array[Double],
spacing : Double,
) -> FeatureVector {
let mut energy = 0.0
for value in values {
energy = energy + value * value
}
{
mean: vector_mean(values),
variance: vector_variance(values),
slope: linear_slope(values, spacing),
minimum: if values.length() == 0 {
0.0
} else {
vector_quantile(values, 0.0)
},
maximum: if values.length() == 0 {
0.0
} else {
vector_quantile(values, 1.0)
},
energy,
}
}