use crate::core::features::{Feature, missing_data::MissingDataHandler};
use std::ops::{Add, Sub, Mul, Div};
#[inline]
fn get_value_with_handler<T: Copy>(
series: &[Option<T>],
index: usize,
handler: &dyn MissingDataHandler<T>,
) -> Option<T> {
series[index].or_else(|| handler.handle(series, index))
}
#[inline]
fn compute_mean<T>(values: &[T]) -> Option<T>
where
T: Copy + Add<Output = T> + Div<Output = T> + From<f64>,
{
if values.is_empty() {
return None;
}
let sum = values.iter().fold(None, |acc: Option<T>, &v| {
Some(acc.map_or(v, |a| a + v))
})?;
Some(sum / T::from(values.len() as f64))
}
#[inline]
fn compute_variance<T>(values: &[T], mean: T) -> Option<T>
where
T: Copy + Sub<Output = T> + Mul<Output = T> + Add<Output = T> + Div<Output = T> + From<f64>,
{
if values.is_empty() {
return None;
}
let sum_sq_diff = values.iter().fold(None, |acc: Option<T>, &v| {
let diff = v - mean;
Some(acc.map_or(diff * diff, |a| a + diff * diff))
})?;
Some(sum_sq_diff / T::from(values.len() as f64))
}
#[inline]
fn collect_window_values<T: Copy>(
series: &[Option<T>],
start: usize,
end: usize,
handler: &dyn MissingDataHandler<T>,
) -> Vec<T> {
(start..end)
.filter_map(|i| get_value_with_handler(series, i, handler))
.collect()
}
pub struct DeltaForwardFeature;
impl<T> Feature<T> for DeltaForwardFeature
where
T: Copy + Sub<Output = T>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index >= series.len() - 1 {
return None;
}
let curr = get_value_with_handler(series, index, handler)?;
let next = get_value_with_handler(series, index + 1, handler)?;
Some(next - curr)
}
fn name(&self) -> &str {
"delta_forward"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct DeltaBackwardFeature;
impl<T> Feature<T> for DeltaBackwardFeature
where
T: Copy + Sub<Output = T>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index == 0 {
return None;
}
let curr = get_value_with_handler(series, index, handler)?;
let prev = get_value_with_handler(series, index - 1, handler)?;
Some(curr - prev)
}
fn name(&self) -> &str {
"delta_backward"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct DeltaSymmetricFeature;
impl<T> Feature<T> for DeltaSymmetricFeature
where
T: Copy + Sub<Output = T> + Div<Output = T> + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index == 0 || index >= series.len() - 1 {
return None;
}
let prev = get_value_with_handler(series, index - 1, handler)?;
let next = get_value_with_handler(series, index + 1, handler)?;
Some((next - prev) / T::from(2.0))
}
fn name(&self) -> &str {
"delta_symmetric"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct LocalSlopeFeature;
impl<T> Feature<T> for LocalSlopeFeature
where
T: Copy + Sub<Output = T> + Div<Output = T> + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
DeltaSymmetricFeature.compute(series, index, handler)
}
fn name(&self) -> &str {
"local_slope"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct AccelerationFeature;
impl<T> Feature<T> for AccelerationFeature
where
T: Copy + Sub<Output = T> + Add<Output = T> + Mul<Output = T> + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index == 0 || index >= series.len() - 1 {
return None;
}
let prev = get_value_with_handler(series, index - 1, handler)?;
let curr = get_value_with_handler(series, index, handler)?;
let next = get_value_with_handler(series, index + 1, handler)?;
Some(next - curr * T::from(2.0) + prev)
}
fn name(&self) -> &str {
"acceleration"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct LocalMeanFeature {
pub window_size: usize,
}
impl<T> Feature<T> for LocalMeanFeature
where
T: Copy + Add<Output = T> + Div<Output = T> + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
let start = index.saturating_sub(self.window_size / 2);
let end = (index + self.window_size / 2 + 1).min(series.len());
let values = collect_window_values(series, start, end, handler);
compute_mean(&values)
}
fn name(&self) -> &str {
"local_mean"
}
fn requires_neighbors(&self) -> bool {
true
}
fn window_size(&self) -> Option<usize> {
Some(self.window_size)
}
}
pub struct LocalVarianceFeature {
pub window_size: usize,
}
impl<T> Feature<T> for LocalVarianceFeature
where
T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
let start = index.saturating_sub(self.window_size / 2);
let end = (index + self.window_size / 2 + 1).min(series.len());
let values = collect_window_values(series, start, end, handler);
let mean = compute_mean(&values)?;
compute_variance(&values, mean)
}
fn name(&self) -> &str {
"local_variance"
}
fn requires_neighbors(&self) -> bool {
true
}
fn window_size(&self) -> Option<usize> {
Some(self.window_size)
}
}
pub struct IsLocalMaxFeature;
impl<T> Feature<T> for IsLocalMaxFeature
where
T: Copy + PartialOrd + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index == 0 || index >= series.len() - 1 {
return Some(T::from(0.0));
}
let curr = get_value_with_handler(series, index, handler)?;
let prev = get_value_with_handler(series, index - 1, handler)?;
let next = get_value_with_handler(series, index + 1, handler)?;
if curr > prev && curr > next {
Some(T::from(1.0))
} else {
Some(T::from(0.0))
}
}
fn name(&self) -> &str {
"is_local_max"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct IsLocalMinFeature;
impl<T> Feature<T> for IsLocalMinFeature
where
T: Copy + PartialOrd + From<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
if index == 0 || index >= series.len() - 1 {
return Some(T::from(0.0));
}
let curr = get_value_with_handler(series, index, handler)?;
let prev = get_value_with_handler(series, index - 1, handler)?;
let next = get_value_with_handler(series, index + 1, handler)?;
if curr < prev && curr < next {
Some(T::from(1.0))
} else {
Some(T::from(0.0))
}
}
fn name(&self) -> &str {
"is_local_min"
}
fn requires_neighbors(&self) -> bool {
true
}
}
pub struct ZScoreFeature;
impl<T> Feature<T> for ZScoreFeature
where
T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + From<f64> + Into<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
let curr = get_value_with_handler(series, index, handler)?;
let values = collect_window_values(series, 0, series.len(), handler);
if values.len() < 2 {
return None;
}
let mean = compute_mean(&values)?;
let variance = compute_variance(&values, mean)?;
let std_val: f64 = variance.into();
let std = T::from(std_val.sqrt());
if std_val > 1e-10 {
Some((curr - mean) / std)
} else {
Some(T::from(0.0))
}
}
fn name(&self) -> &str {
"z_score"
}
fn requires_neighbors(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy)]
pub enum BuiltinFeature {
DeltaForward,
DeltaBackward,
DeltaSymmetric,
LocalSlope,
Acceleration,
LocalMean,
LocalVariance,
IsLocalMax,
IsLocalMin,
ZScore,
}
impl<T> Feature<T> for BuiltinFeature
where
T: Copy + PartialOrd + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + From<f64> + Into<f64>,
{
fn compute(&self, series: &[Option<T>], index: usize, handler: &dyn MissingDataHandler<T>) -> Option<T> {
match self {
BuiltinFeature::DeltaForward => DeltaForwardFeature.compute(series, index, handler),
BuiltinFeature::DeltaBackward => DeltaBackwardFeature.compute(series, index, handler),
BuiltinFeature::DeltaSymmetric => DeltaSymmetricFeature.compute(series, index, handler),
BuiltinFeature::LocalSlope => LocalSlopeFeature.compute(series, index, handler),
BuiltinFeature::Acceleration => AccelerationFeature.compute(series, index, handler),
BuiltinFeature::LocalMean => LocalMeanFeature { window_size: 3 }.compute(series, index, handler),
BuiltinFeature::LocalVariance => LocalVarianceFeature { window_size: 3 }.compute(series, index, handler),
BuiltinFeature::IsLocalMax => IsLocalMaxFeature.compute(series, index, handler),
BuiltinFeature::IsLocalMin => IsLocalMinFeature.compute(series, index, handler),
BuiltinFeature::ZScore => ZScoreFeature.compute(series, index, handler),
}
}
fn name(&self) -> &str {
match self {
BuiltinFeature::DeltaForward => "delta_forward",
BuiltinFeature::DeltaBackward => "delta_backward",
BuiltinFeature::DeltaSymmetric => "delta_symmetric",
BuiltinFeature::LocalSlope => "local_slope",
BuiltinFeature::Acceleration => "acceleration",
BuiltinFeature::LocalMean => "local_mean",
BuiltinFeature::LocalVariance => "local_variance",
BuiltinFeature::IsLocalMax => "is_local_max",
BuiltinFeature::IsLocalMin => "is_local_min",
BuiltinFeature::ZScore => "z_score",
}
}
fn requires_neighbors(&self) -> bool {
!matches!(self, BuiltinFeature::ZScore)
}
}