From a7f992af66b2b348a0f1d0adcd691953e78ccfaa Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sat, 28 Feb 2026 14:40:16 -0500 Subject: [PATCH 1/2] AggregateFn MinMax Signed-off-by: Nicholas Gates --- .../fns/min_max/bool_accumulator.rs | 124 +++++++ .../src/aggregate_fn/fns/min_max/mod.rs | 186 ++++++++++ .../fns/min_max/primitive_accumulator.rs | 121 +++++++ .../src/aggregate_fn/fns/min_max/tests.rs | 335 ++++++++++++++++++ vortex-array/src/aggregate_fn/fns/mod.rs | 1 + vortex-array/src/aggregate_fn/session.rs | 4 + 6 files changed, 771 insertions(+) create mode 100644 vortex-array/src/aggregate_fn/fns/min_max/bool_accumulator.rs create mode 100644 vortex-array/src/aggregate_fn/fns/min_max/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/min_max/primitive_accumulator.rs create mode 100644 vortex-array/src/aggregate_fn/fns/min_max/tests.rs diff --git a/vortex-array/src/aggregate_fn/fns/min_max/bool_accumulator.rs b/vortex-array/src/aggregate_fn/fns/min_max/bool_accumulator.rs new file mode 100644 index 00000000000..9923196e3b5 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/min_max/bool_accumulator.rs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::marker::PhantomData; +use std::ops::BitAnd; + +use vortex_error::VortexResult; +use vortex_mask::Mask; + +use super::Direction; +use crate::ArrayRef; +use crate::IntoArray; +use crate::aggregate_fn::accumulator::Accumulator; +use crate::arrays::BoolArray; +use crate::canonical::ToCanonical; +use crate::scalar::Scalar; + +/// Accumulator for boolean min/max. +/// +/// - Min is saturated as soon as `false` is seen (since false < true). +/// - Max is saturated as soon as `true` is seen. +pub(super) struct BoolExtremumAccumulator { + current: Option, + results: Vec>, + _direction: PhantomData, +} + +impl BoolExtremumAccumulator { + pub(super) fn new() -> Self { + Self { + current: None, + results: Vec::new(), + _direction: PhantomData, + } + } + + #[inline] + fn consider(&mut self, candidate: bool) { + match self.current { + None => self.current = Some(candidate), + Some(cur) => { + if D::should_replace_bool(cur, candidate) { + self.current = Some(candidate); + } + } + } + } +} + +/// Count of true and false values in a boolean array, considering validity. +struct BoolCounts { + true_count: u64, + false_count: u64, +} + +fn bool_counts(bool_array: &BoolArray) -> VortexResult { + let validity = bool_array.validity_mask()?; + let bits = bool_array.to_bit_buffer(); + + match &validity { + Mask::AllTrue(_) => { + let true_count = bits.true_count() as u64; + let false_count = bool_array.len() as u64 - true_count; + Ok(BoolCounts { + true_count, + false_count, + }) + } + Mask::AllFalse(_) => Ok(BoolCounts { + true_count: 0, + false_count: 0, + }), + Mask::Values(v) => { + let valid_bits = bits.bitand(v.bit_buffer()); + let true_count = valid_bits.true_count() as u64; + let valid_count = v.bit_buffer().true_count() as u64; + let false_count = valid_count - true_count; + Ok(BoolCounts { + true_count, + false_count, + }) + } + } +} + +impl Accumulator for BoolExtremumAccumulator { + fn accumulate(&mut self, batch: &ArrayRef) -> VortexResult<()> { + let bool_array = batch.to_bool(); + let counts = bool_counts(&bool_array)?; + + if counts.true_count > 0 { + self.consider(true); + } + if counts.false_count > 0 { + self.consider(false); + } + + Ok(()) + } + + fn merge(&mut self, state: &Scalar) -> VortexResult<()> { + if state.is_null() { + return Ok(()); + } + if let Some(v) = state.as_bool().value() { + self.consider(v); + } + Ok(()) + } + + fn is_saturated(&self) -> bool { + self.current.is_some_and(D::is_saturated_bool) + } + + fn flush(&mut self) -> VortexResult<()> { + self.results.push(self.current); + self.current = None; + Ok(()) + } + + fn finish(self: Box) -> VortexResult { + Ok(BoolArray::from_iter(self.results).into_array()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/min_max/mod.rs b/vortex-array/src/aggregate_fn/fns/min_max/mod.rs new file mode 100644 index 00000000000..a6f06fb3228 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/min_max/mod.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +mod bool_accumulator; +mod primitive_accumulator; + +use num_traits::Bounded; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; + +use self::bool_accumulator::BoolExtremumAccumulator; +use self::primitive_accumulator::PrimitiveExtremumAccumulator; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::accumulator::Accumulator; +use crate::dtype::DType; +use crate::dtype::NativePType; +use crate::dtype::Nullability; +use crate::match_each_native_ptype; +use crate::scalar_fn::EmptyOptions; + +/// Compile-time direction for extremum accumulators. +pub(crate) trait Direction: Send + Sync + 'static { + /// Returns `true` if `candidate` should replace `current`. + fn should_replace(current: T, candidate: T) -> bool; + + /// Returns `true` if `value` is at the type's extreme and no further improvement is possible. + fn is_saturated(value: T) -> bool; + + /// Returns `true` if `candidate` should replace `current` for booleans. + fn should_replace_bool(current: bool, candidate: bool) -> bool; + + /// Returns `true` if the boolean value is saturated. + fn is_saturated_bool(value: bool) -> bool; +} + +/// Seek the minimum value. +pub(crate) struct FindMin; + +impl Direction for FindMin { + #[inline] + fn should_replace(current: T, candidate: T) -> bool { + candidate.is_lt(current) + } + + #[inline] + fn is_saturated(value: T) -> bool { + value.total_compare(T::min_value()).is_eq() + } + + #[inline] + fn should_replace_bool(_current: bool, candidate: bool) -> bool { + !candidate + } + + #[inline] + fn is_saturated_bool(value: bool) -> bool { + !value + } +} + +/// Seek the maximum value. +pub(crate) struct FindMax; + +impl Direction for FindMax { + #[inline] + fn should_replace(current: T, candidate: T) -> bool { + candidate.is_gt(current) + } + + #[inline] + fn is_saturated(value: T) -> bool { + value.total_compare(T::max_value()).is_eq() + } + + #[inline] + fn should_replace_bool(_current: bool, candidate: bool) -> bool { + candidate + } + + #[inline] + fn is_saturated_bool(value: bool) -> bool { + value + } +} + +/// Computes the minimum of numeric or boolean values. +/// +/// Nulls and NaN values are skipped. The output dtype matches the input dtype but is always +/// nullable. +/// +/// # Flush semantics +/// +/// - **Empty group** (no accumulate/merge calls): produces **null**. +/// - **All-null group**: produces **null**. +/// - `is_saturated()` returns true once the type's minimum value is seen. +#[derive(Clone)] +pub struct Min; + +/// Computes the maximum of numeric or boolean values. +/// +/// Nulls and NaN values are skipped. The output dtype matches the input dtype but is always +/// nullable. +/// +/// # Flush semantics +/// +/// - **Empty group** (no accumulate/merge calls): produces **null**. +/// - **All-null group**: produces **null**. +/// - `is_saturated()` returns true once the type's maximum value is seen. +#[derive(Clone)] +pub struct Max; + +fn return_dtype(input_dtype: &DType) -> VortexResult { + match input_dtype { + DType::Bool(_) => Ok(DType::Bool(Nullability::Nullable)), + DType::Primitive(p, _) => Ok(DType::Primitive(*p, Nullability::Nullable)), + _ => vortex_bail!( + "Min/Max requires numeric or boolean input, got {}", + input_dtype + ), + } +} + +fn make_accumulator(input_dtype: &DType) -> VortexResult> { + match input_dtype { + DType::Bool(_) => Ok(Box::new(BoolExtremumAccumulator::::new())), + DType::Primitive(p, _) => Ok(match_each_native_ptype!(*p, |T| { + Box::new(PrimitiveExtremumAccumulator::::new()) as Box + })), + _ => vortex_bail!( + "Min/Max requires numeric or boolean input, got {}", + input_dtype + ), + } +} + +impl AggregateFnVTable for Min { + type Options = EmptyOptions; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new_ref("vortex.min") + } + + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> VortexResult { + return_dtype(input_dtype) + } + + fn state_dtype(&self, options: &Self::Options, input_dtype: &DType) -> VortexResult { + self.return_dtype(options, input_dtype) + } + + fn accumulator( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult> { + make_accumulator::(input_dtype) + } +} + +impl AggregateFnVTable for Max { + type Options = EmptyOptions; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new_ref("vortex.max") + } + + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> VortexResult { + return_dtype(input_dtype) + } + + fn state_dtype(&self, options: &Self::Options, input_dtype: &DType) -> VortexResult { + self.return_dtype(options, input_dtype) + } + + fn accumulator( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult> { + make_accumulator::(input_dtype) + } +} + +#[cfg(test)] +mod tests; diff --git a/vortex-array/src/aggregate_fn/fns/min_max/primitive_accumulator.rs b/vortex-array/src/aggregate_fn/fns/min_max/primitive_accumulator.rs new file mode 100644 index 00000000000..a5bb08850e0 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/min_max/primitive_accumulator.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::marker::PhantomData; + +use num_traits::Bounded; +use vortex_error::VortexResult; +use vortex_mask::Mask; + +use super::Direction; +use crate::ArrayRef; +use crate::IntoArray; +use crate::aggregate_fn::accumulator::Accumulator; +use crate::arrays::PrimitiveArray; +use crate::canonical::ToCanonical; +use crate::dtype::NativePType; +use crate::scalar::PValue; +use crate::scalar::Scalar; + +pub(super) struct PrimitiveExtremumAccumulator { + current: Option, + results: Vec>, + _direction: PhantomData, +} + +impl PrimitiveExtremumAccumulator +where + PValue: From, +{ + pub(super) fn new() -> Self { + Self { + current: None, + results: Vec::new(), + _direction: PhantomData, + } + } + + #[inline] + fn consider(&mut self, candidate: T) { + match self.current { + None => self.current = Some(candidate), + Some(cur) => { + if D::should_replace(cur, candidate) { + self.current = Some(candidate); + } + } + } + } +} + +fn accumulate_all_valid( + values: &[T], + acc: &mut PrimitiveExtremumAccumulator, +) where + PValue: From, +{ + for &v in values { + if !v.is_nan() { + acc.consider(v); + } + } +} + +fn accumulate_with_mask( + values: &[T], + mask: &vortex_mask::MaskValues, + acc: &mut PrimitiveExtremumAccumulator, +) where + PValue: From, +{ + for (&v, valid) in values.iter().zip(mask.bit_buffer().iter()) { + if valid && !v.is_nan() { + acc.consider(v); + } + } +} + +impl Accumulator for PrimitiveExtremumAccumulator +where + PValue: From, +{ + fn accumulate(&mut self, batch: &ArrayRef) -> VortexResult<()> { + let primitive = batch.to_primitive(); + let validity = primitive.validity_mask()?; + let values = primitive.as_slice::(); + + match &validity { + Mask::AllTrue(_) => accumulate_all_valid(values, self), + Mask::AllFalse(_) => {} + Mask::Values(v) => accumulate_with_mask(values, v, self), + } + + Ok(()) + } + + fn merge(&mut self, state: &Scalar) -> VortexResult<()> { + if state.is_null() { + return Ok(()); + } + if let Some(v) = state.as_primitive().typed_value::() + && !v.is_nan() + { + self.consider(v); + } + Ok(()) + } + + fn is_saturated(&self) -> bool { + self.current.is_some_and(D::is_saturated) + } + + fn flush(&mut self) -> VortexResult<()> { + self.results.push(self.current); + self.current = None; + Ok(()) + } + + fn finish(self: Box) -> VortexResult { + Ok(PrimitiveArray::from_option_iter(self.results).into_array()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/min_max/tests.rs b/vortex-array/src/aggregate_fn/fns/min_max/tests.rs new file mode 100644 index 00000000000..11517cdae34 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/min_max/tests.rs @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_buffer::buffer; +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::fns::min_max::Max; +use crate::aggregate_fn::fns::min_max::Min; +use crate::arrays::BoolArray; +use crate::arrays::PrimitiveArray; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::dtype::PType; +use crate::scalar::Scalar; +use crate::scalar_fn::EmptyOptions; +use crate::validity::Validity; + +fn run_min(batch: &ArrayRef) -> VortexResult { + let mut acc = Min.accumulator(&EmptyOptions, batch.dtype())?; + acc.accumulate(batch)?; + acc.flush()?; + acc.finish() +} + +fn run_max(batch: &ArrayRef) -> VortexResult { + let mut acc = Max.accumulator(&EmptyOptions, batch.dtype())?; + acc.accumulate(batch)?; + acc.flush()?; + acc.finish() +} + +fn get_i32_value(array: &ArrayRef, idx: usize) -> VortexResult> { + let scalar = array.scalar_at(idx)?; + Ok(scalar.as_primitive().typed_value::()) +} + +fn get_f64_value(array: &ArrayRef, idx: usize) -> VortexResult> { + let scalar = array.scalar_at(idx)?; + Ok(scalar.as_primitive().typed_value::()) +} + +fn get_bool_value(array: &ArrayRef, idx: usize) -> VortexResult> { + let scalar = array.scalar_at(idx)?; + Ok(scalar.as_bool().value()) +} + +// -- Min tests -- + +#[test] +fn min_i32() -> VortexResult<()> { + let arr = PrimitiveArray::new(buffer![3i32, 1, 4, 1, 5], Validity::NonNullable).into_array(); + let result = run_min(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, Some(1)); + Ok(()) +} + +#[test] +fn max_i32() -> VortexResult<()> { + let arr = PrimitiveArray::new(buffer![3i32, 1, 4, 1, 5], Validity::NonNullable).into_array(); + let result = run_max(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, Some(5)); + Ok(()) +} + +#[test] +fn min_f64() -> VortexResult<()> { + let arr = PrimitiveArray::new(buffer![3.0f64, 1.5, 2.0], Validity::NonNullable).into_array(); + let result = run_min(&arr)?; + assert_eq!(get_f64_value(&result, 0)?, Some(1.5)); + Ok(()) +} + +#[test] +fn max_f64() -> VortexResult<()> { + let arr = PrimitiveArray::new(buffer![3.0f64, 1.5, 2.0], Validity::NonNullable).into_array(); + let result = run_max(&arr)?; + assert_eq!(get_f64_value(&result, 0)?, Some(3.0)); + Ok(()) +} + +#[test] +fn min_with_nulls() -> VortexResult<()> { + let arr = PrimitiveArray::from_option_iter([Some(5i32), None, Some(2)]).into_array(); + let result = run_min(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, Some(2)); + Ok(()) +} + +#[test] +fn max_with_nulls() -> VortexResult<()> { + let arr = PrimitiveArray::from_option_iter([Some(5i32), None, Some(2)]).into_array(); + let result = run_max(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, Some(5)); + Ok(()) +} + +#[test] +fn min_all_null() -> VortexResult<()> { + let arr = PrimitiveArray::from_option_iter([None::, None, None]).into_array(); + let result = run_min(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn max_all_null() -> VortexResult<()> { + let arr = PrimitiveArray::from_option_iter([None::, None, None]).into_array(); + let result = run_max(&arr)?; + assert_eq!(get_i32_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn min_empty_flush_produces_null() -> VortexResult<()> { + let mut acc = Min.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + acc.flush()?; + let result = acc.finish()?; + assert_eq!(get_i32_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn max_empty_flush_produces_null() -> VortexResult<()> { + let mut acc = Max.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + acc.flush()?; + let result = acc.finish()?; + assert_eq!(get_i32_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn min_multi_group() -> VortexResult<()> { + let mut acc = Min.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + + let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array(); + acc.accumulate(&batch1)?; + acc.flush()?; + + let batch2 = PrimitiveArray::new(buffer![3i32, 6, 1], Validity::NonNullable).into_array(); + acc.accumulate(&batch2)?; + acc.flush()?; + + let result = acc.finish()?; + assert_eq!(get_i32_value(&result, 0)?, Some(10)); + assert_eq!(get_i32_value(&result, 1)?, Some(1)); + Ok(()) +} + +#[test] +fn min_merge() -> VortexResult<()> { + let mut acc = Min.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + + let state1 = Scalar::primitive(100i32, Nullability::Nullable); + acc.merge(&state1)?; + + let state2 = Scalar::primitive(50i32, Nullability::Nullable); + acc.merge(&state2)?; + + acc.flush()?; + let result = acc.finish()?; + assert_eq!(get_i32_value(&result, 0)?, Some(50)); + Ok(()) +} + +#[test] +fn min_is_saturated() -> VortexResult<()> { + let mut acc = Min.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + assert!(!acc.is_saturated()); + + let batch = PrimitiveArray::new(buffer![i32::MIN, 5i32], Validity::NonNullable).into_array(); + acc.accumulate(&batch)?; + assert!(acc.is_saturated()); + Ok(()) +} + +#[test] +fn max_is_saturated() -> VortexResult<()> { + let mut acc = Max.accumulator( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + assert!(!acc.is_saturated()); + + let batch = PrimitiveArray::new(buffer![i32::MAX, 5i32], Validity::NonNullable).into_array(); + acc.accumulate(&batch)?; + assert!(acc.is_saturated()); + Ok(()) +} + +// NaN handling + +#[test] +fn min_skips_nan() -> VortexResult<()> { + let arr = + PrimitiveArray::new(buffer![f64::NAN, 3.0f64, 1.0], Validity::NonNullable).into_array(); + let result = run_min(&arr)?; + assert_eq!(get_f64_value(&result, 0)?, Some(1.0)); + Ok(()) +} + +#[test] +fn max_skips_nan() -> VortexResult<()> { + let arr = + PrimitiveArray::new(buffer![f64::NAN, 3.0f64, 1.0], Validity::NonNullable).into_array(); + let result = run_max(&arr)?; + assert_eq!(get_f64_value(&result, 0)?, Some(3.0)); + Ok(()) +} + +// Boolean min/max + +#[test] +fn min_bool_mixed() -> VortexResult<()> { + let arr: BoolArray = [true, false, true].into_iter().collect(); + let result = run_min(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, Some(false)); + Ok(()) +} + +#[test] +fn max_bool_mixed() -> VortexResult<()> { + let arr: BoolArray = [false, true, false].into_iter().collect(); + let result = run_max(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, Some(true)); + Ok(()) +} + +#[test] +fn min_bool_all_true() -> VortexResult<()> { + let arr: BoolArray = [true, true, true].into_iter().collect(); + let result = run_min(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, Some(true)); + Ok(()) +} + +#[test] +fn max_bool_all_false() -> VortexResult<()> { + let arr: BoolArray = [false, false, false].into_iter().collect(); + let result = run_max(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, Some(false)); + Ok(()) +} + +#[test] +fn min_bool_with_nulls() -> VortexResult<()> { + let arr = BoolArray::from_iter([Some(true), None, Some(false)]); + let result = run_min(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, Some(false)); + Ok(()) +} + +#[test] +fn max_bool_all_null() -> VortexResult<()> { + let arr = BoolArray::from_iter([None::, None]); + let result = run_max(&arr.into_array())?; + assert_eq!(get_bool_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn min_bool_empty_flush_produces_null() -> VortexResult<()> { + let mut acc = Min.accumulator(&EmptyOptions, &DType::Bool(Nullability::NonNullable))?; + acc.flush()?; + let result = acc.finish()?; + assert_eq!(get_bool_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn max_bool_empty_flush_produces_null() -> VortexResult<()> { + let mut acc = Max.accumulator(&EmptyOptions, &DType::Bool(Nullability::NonNullable))?; + acc.flush()?; + let result = acc.finish()?; + assert_eq!(get_bool_value(&result, 0)?, None); + Ok(()) +} + +#[test] +fn min_bool_saturated() -> VortexResult<()> { + let mut acc = Min.accumulator(&EmptyOptions, &DType::Bool(Nullability::NonNullable))?; + assert!(!acc.is_saturated()); + + let batch: BoolArray = [true, false].into_iter().collect(); + acc.accumulate(&batch.into_array())?; + assert!(acc.is_saturated()); + Ok(()) +} + +#[test] +fn max_bool_saturated() -> VortexResult<()> { + let mut acc = Max.accumulator(&EmptyOptions, &DType::Bool(Nullability::NonNullable))?; + assert!(!acc.is_saturated()); + + let batch: BoolArray = [false, true].into_iter().collect(); + acc.accumulate(&batch.into_array())?; + assert!(acc.is_saturated()); + Ok(()) +} + +// Return dtype + +#[test] +fn min_return_dtype() -> VortexResult<()> { + let dtype = Min.return_dtype( + &EmptyOptions, + &DType::Primitive(PType::I32, Nullability::NonNullable), + )?; + assert_eq!(dtype, DType::Primitive(PType::I32, Nullability::Nullable)); + Ok(()) +} + +#[test] +fn max_return_dtype_bool() -> VortexResult<()> { + let dtype = Max.return_dtype(&EmptyOptions, &DType::Bool(Nullability::NonNullable))?; + assert_eq!(dtype, DType::Bool(Nullability::Nullable)); + Ok(()) +} diff --git a/vortex-array/src/aggregate_fn/fns/mod.rs b/vortex-array/src/aggregate_fn/fns/mod.rs index 777480d0e7a..bdacbf0c3d5 100644 --- a/vortex-array/src/aggregate_fn/fns/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/mod.rs @@ -2,4 +2,5 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors pub mod mean; +pub mod min_max; pub mod sum; diff --git a/vortex-array/src/aggregate_fn/session.rs b/vortex-array/src/aggregate_fn/session.rs index 6ec6a10b735..f310065ac1f 100644 --- a/vortex-array/src/aggregate_fn/session.rs +++ b/vortex-array/src/aggregate_fn/session.rs @@ -10,6 +10,8 @@ use vortex_session::registry::Registry; use crate::aggregate_fn::AggregateFnPluginRef; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::fns::mean::Mean; +use crate::aggregate_fn::fns::min_max::Max; +use crate::aggregate_fn::fns::min_max::Min; use crate::aggregate_fn::fns::sum::Sum; /// Registry of aggregate function vtables. @@ -27,6 +29,8 @@ impl Default for AggregateFnSession { registry: AggregateFnRegistry::default(), }; session.register(Mean); + session.register(Min); + session.register(Max); session.register(Sum); session } From 5e9fa6ba36f2fd39bb890755714b7a03fc3e22ce Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sat, 28 Feb 2026 14:40:34 -0500 Subject: [PATCH 2/2] AggregateFn MinMax Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 152 +++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index 1d9df743c74..0df06bdda38 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -54,6 +54,110 @@ pub fn vortex_array::aggregate_fn::fns::mean::Mean::serialize(&self, options: &S pub fn vortex_array::aggregate_fn::fns::mean::Mean::state_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult +pub mod vortex_array::aggregate_fn::fns::min_max + +pub struct vortex_array::aggregate_fn::fns::min_max::Max + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::min_max::Max + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::clone(&self) -> vortex_array::aggregate_fn::fns::min_max::Max + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::Max + +pub type vortex_array::aggregate_fn::fns::min_max::Max::Options = vortex_array::scalar_fn::EmptyOptions + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::accumulator(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::min_max::Min + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::min_max::Min + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::clone(&self) -> vortex_array::aggregate_fn::fns::min_max::Min + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::Min + +pub type vortex_array::aggregate_fn::fns::min_max::Min::Options = vortex_array::scalar_fn::EmptyOptions + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::accumulator(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub mod vortex_array::aggregate_fn::fns::sum + +pub struct vortex_array::aggregate_fn::fns::sum::Sum + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::sum::Sum + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::clone(&self) -> vortex_array::aggregate_fn::fns::sum::Sum + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::sum::Sum + +pub type vortex_array::aggregate_fn::fns::sum::Sum::Options = vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::accumulator(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::sum::SumOptions + +pub vortex_array::aggregate_fn::fns::sum::SumOptions::checked: bool + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::clone(&self) -> vortex_array::aggregate_fn::fns::sum::SumOptions + +impl core::cmp::Eq for vortex_array::aggregate_fn::fns::sum::SumOptions + +impl core::cmp::PartialEq for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::eq(&self, other: &vortex_array::aggregate_fn::fns::sum::SumOptions) -> bool + +impl core::default::Default for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::default() -> vortex_array::aggregate_fn::fns::sum::SumOptions + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::fmt::Display for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::hash::Hash for vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::SumOptions::hash<__H: core::hash::Hasher>(&self, state: &mut __H) + +impl core::marker::Copy for vortex_array::aggregate_fn::fns::sum::SumOptions + +impl core::marker::StructuralPartialEq for vortex_array::aggregate_fn::fns::sum::SumOptions + pub mod vortex_array::aggregate_fn::session pub struct vortex_array::aggregate_fn::session::AggregateFnSession @@ -226,6 +330,54 @@ pub fn vortex_array::aggregate_fn::fns::mean::Mean::serialize(&self, options: &S pub fn vortex_array::aggregate_fn::fns::mean::Mean::state_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::Max + +pub type vortex_array::aggregate_fn::fns::min_max::Max::Options = vortex_array::scalar_fn::EmptyOptions + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::accumulator(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min_max::Max::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::Min + +pub type vortex_array::aggregate_fn::fns::min_max::Min::Options = vortex_array::scalar_fn::EmptyOptions + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::accumulator(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min_max::Min::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::sum::Sum + +pub type vortex_array::aggregate_fn::fns::sum::Sum::Options = vortex_array::aggregate_fn::fns::sum::SumOptions + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::accumulator(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::sum::Sum::state_dtype(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + pub trait vortex_array::aggregate_fn::AggregateFnVTableExt: vortex_array::aggregate_fn::AggregateFnVTable pub fn vortex_array::aggregate_fn::AggregateFnVTableExt::bind(&self, options: Self::Options) -> vortex_array::aggregate_fn::AggregateFnRef