use arrow::array::ArrayRef;
use arrow::datatypes::{DataType, Field};
use datafusion::common::Result as DFResult;
use super::window::{
column_f64, constant_f64, constant_usize, exec_err, f64_array, on_dense, PartitionKernel,
};
macro_rules! kernel {
(
$(#[$meta:meta])*
$name:ident, $sql:literal, [$($ty:expr),* $(,)?]
$(, returns: $ret:expr)?
, |$args:ident, $rows:ident| $body:block
) => {
$(#[$meta])*
#[derive(Debug)]
pub(super) struct $name;
impl PartitionKernel for $name {
const NAME: &'static str = $sql;
fn arg_types() -> Vec<DataType> {
vec![$($ty),*]
}
$(fn return_type() -> DataType { $ret })?
fn evaluate($args: &[ArrayRef], $rows: usize) -> DFResult<ArrayRef> $body
}
};
}
kernel! {
DiffKernel, "diff", [DataType::Float64, DataType::Int64],
|args, _rows| {
let values = column_f64(args, 0)?;
let order = constant_usize(args, 1, "diff order")?;
Ok(f64_array(chronix_analytics::preprocess::diff(&values, order)))
}
}
kernel! {
PctChangeKernel, "pct_change", [DataType::Float64],
|args, _rows| {
let values = column_f64(args, 0)?;
Ok(f64_array(chronix_analytics::preprocess::pct_change(&values)))
}
}
kernel! {
RollingMeanKernel, "rolling_mean", [DataType::Float64, DataType::Int64],
|args, _rows| {
let values = column_f64(args, 0)?;
let window = constant_usize(args, 1, "rolling_mean window")?;
Ok(f64_array(chronix_analytics::preprocess::rolling_mean(&values, window)))
}
}
kernel! {
RollingStdKernel, "rolling_std", [DataType::Float64, DataType::Int64],
|args, _rows| {
let values = column_f64(args, 0)?;
let window = constant_usize(args, 1, "rolling_std window")?;
Ok(f64_array(chronix_analytics::preprocess::rolling_std(&values, window)))
}
}
kernel! {
RollingCorrKernel, "rolling_corr",
[DataType::Float64, DataType::Float64, DataType::Int64],
|args, _rows| {
let a = column_f64(args, 0)?;
let b = column_f64(args, 1)?;
let window = constant_usize(args, 2, "rolling_corr window")?;
Ok(f64_array(chronix_analytics::preprocess::rolling_corr(&a, &b, window)))
}
}
kernel! {
ZscoreKernel, "zscore", [DataType::Float64],
|args, _rows| {
let values = column_f64(args, 0)?;
Ok(f64_array(chronix_analytics::preprocess::zscore(&values)))
}
}
kernel! {
EwmKernel, "ewm", [DataType::Float64, DataType::Float64],
|args, _rows| {
let values = column_f64(args, 0)?;
let alpha = constant_f64(args, 1, "ewm alpha")?;
if !(alpha > 0.0 && alpha <= 1.0) {
return Err(datafusion::common::DataFusionError::Plan(format!(
"ewm alpha must be in (0, 1], got {alpha}"
)));
}
Ok(f64_array(chronix_analytics::preprocess::ewm(&values, alpha)))
}
}
#[derive(Debug, Clone, Copy)]
enum StlPart {
Trend,
Seasonal,
Residual,
}
fn stl_component(args: &[ArrayRef], part: StlPart) -> DFResult<ArrayRef> {
use chronix_analytics::preprocess::{stl_decompose, StlConfig};
let values = column_f64(args, 0)?;
let period = constant_usize(args, 1, "stl period")?;
let out = on_dense(&values, |dense| {
let d = stl_decompose(dense, &StlConfig::new(period)).map_err(exec_err)?;
Ok(match part {
StlPart::Trend => d.trend,
StlPart::Seasonal => d.seasonal,
StlPart::Residual => d.residual,
})
})?;
Ok(f64_array(out))
}
kernel! {
StlTrendKernel, "stl_trend", [DataType::Float64, DataType::Int64],
|args, _rows| { stl_component(args, StlPart::Trend) }
}
kernel! {
StlSeasonalKernel, "stl_seasonal", [DataType::Float64, DataType::Int64],
|args, _rows| { stl_component(args, StlPart::Seasonal) }
}
kernel! {
StlResidualKernel, "stl_residual", [DataType::Float64, DataType::Int64],
|args, _rows| { stl_component(args, StlPart::Residual) }
}
fn stl_struct_fields() -> arrow::datatypes::Fields {
vec![
Field::new("trend", DataType::Float64, true),
Field::new("seasonal", DataType::Float64, true),
Field::new("residual", DataType::Float64, true),
]
.into()
}
kernel! {
StlDecomposeKernel, "stl_decompose", [DataType::Float64, DataType::Int64],
returns: DataType::Struct(stl_struct_fields()),
|args, _rows| {
use std::sync::Arc;
use arrow::array::StructArray;
use chronix_analytics::preprocess::{stl_decompose, StlConfig};
let values = column_f64(args, 0)?;
let period = constant_usize(args, 1, "stl period")?;
if period < 2 {
return Err(datafusion::common::DataFusionError::Plan(
"stl_decompose: period must be at least 2".into(),
));
}
let dense: Vec<f64> = values.iter().copied().filter(|v| v.is_finite()).collect();
if dense.len() < period * 2 {
return Err(datafusion::common::DataFusionError::Plan(format!(
"stl_decompose: need at least 2*period ({}) non-null rows, got {}",
period * 2,
dense.len()
)));
}
let d = stl_decompose(&dense, &StlConfig::new(period)).map_err(exec_err)?;
let spread = |src: &[f64]| {
let mut out = vec![f64::NAN; values.len()];
let mut k = 0;
for (i, v) in values.iter().enumerate() {
if v.is_finite() {
if let Some(c) = src.get(k) {
out[i] = *c;
}
k += 1;
}
}
f64_array(out)
};
let arr = StructArray::try_new(
stl_struct_fields(),
vec![spread(&d.trend), spread(&d.seasonal), spread(&d.residual)],
None,
)
.map_err(exec_err)?;
Ok(Arc::new(arr))
}
}