Skip to content

Commit d1fc11f

Browse files
committed
MinMaxAggregateFn
Signed-off-by: Nicholas Gates <nick@nickgates.com>
1 parent 2927bfd commit d1fc11f

3 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use vortex_array::ArrayRef;
5+
use vortex_array::ExecutionCtx;
6+
use vortex_array::aggregate_fn::AggregateFnRef;
7+
use vortex_array::aggregate_fn::fns::min_max::MinMax;
8+
use vortex_array::aggregate_fn::fns::min_max::make_struct_dtype;
9+
use vortex_array::aggregate_fn::fns::min_max::min_max;
10+
use vortex_array::aggregate_fn::kernels::DynAggregateKernel;
11+
use vortex_array::scalar::Scalar;
12+
use vortex_error::VortexResult;
13+
14+
use crate::RunEnd;
15+
16+
/// RunEnd-specific min/max kernel.
17+
///
18+
/// Run-end encoded arrays store each unique run value once, so min/max can be computed directly
19+
/// on the values array without decoding.
20+
#[derive(Debug)]
21+
pub(crate) struct RunEndMinMaxKernel;
22+
23+
impl DynAggregateKernel for RunEndMinMaxKernel {
24+
fn aggregate(
25+
&self,
26+
aggregate_fn: &AggregateFnRef,
27+
batch: &ArrayRef,
28+
ctx: &mut ExecutionCtx,
29+
) -> VortexResult<Option<Scalar>> {
30+
if !aggregate_fn.is::<MinMax>() {
31+
return Ok(None);
32+
}
33+
34+
let Some(run_end) = batch.as_opt::<RunEnd>() else {
35+
return Ok(None);
36+
};
37+
38+
let struct_dtype = make_struct_dtype(batch.dtype());
39+
match min_max(run_end.values(), ctx)? {
40+
Some(result) => Ok(Some(Scalar::struct_(
41+
struct_dtype,
42+
vec![result.min, result.max],
43+
))),
44+
None => Ok(Some(Scalar::null(struct_dtype))),
45+
}
46+
}
47+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use vortex_array::ArrayRef;
5+
use vortex_array::ExecutionCtx;
6+
use vortex_array::aggregate_fn::AggregateFnRef;
7+
use vortex_array::aggregate_fn::fns::min_max::MinMax;
8+
use vortex_array::aggregate_fn::fns::min_max::make_struct_dtype;
9+
use vortex_array::aggregate_fn::kernels::DynAggregateKernel;
10+
use vortex_array::dtype::DType;
11+
use vortex_array::dtype::Nullability;
12+
use vortex_array::match_each_pvalue;
13+
use vortex_array::scalar::PValue;
14+
use vortex_array::scalar::Scalar;
15+
use vortex_array::scalar::ScalarValue;
16+
use vortex_error::VortexResult;
17+
18+
use crate::Sequence;
19+
20+
/// Sequence-specific min/max kernel.
21+
///
22+
/// A sequence array represents `A[i] = base + i * multiplier`, so min/max can be computed
23+
/// algebraically from `base` and `last` based on the sign of the multiplier.
24+
#[derive(Debug)]
25+
pub(crate) struct SequenceMinMaxKernel;
26+
27+
impl DynAggregateKernel for SequenceMinMaxKernel {
28+
fn aggregate(
29+
&self,
30+
aggregate_fn: &AggregateFnRef,
31+
batch: &ArrayRef,
32+
_ctx: &mut ExecutionCtx,
33+
) -> VortexResult<Option<Scalar>> {
34+
if !aggregate_fn.is::<MinMax>() {
35+
return Ok(None);
36+
}
37+
38+
let Some(seq) = batch.as_opt::<Sequence>() else {
39+
return Ok(None);
40+
};
41+
42+
let struct_dtype = make_struct_dtype(batch.dtype());
43+
44+
// Empty sequences shouldn't exist (try_new validates length), but handle gracefully.
45+
if seq.is_empty() {
46+
return Ok(Some(Scalar::null(struct_dtype)));
47+
}
48+
49+
let base = seq.base();
50+
let last = seq.last();
51+
52+
// Determine min and max based on multiplier direction.
53+
// For unsigned types, multiplier is always >= 0.
54+
let (min_pvalue, max_pvalue) = match_each_pvalue!(
55+
seq.multiplier(),
56+
uint: |_v| { (base, last) },
57+
int: |v| {
58+
if v >= 0 {
59+
(base, last)
60+
} else {
61+
(last, base)
62+
}
63+
},
64+
float: |_v| { unreachable!("float multiplier not supported for SequenceArray") }
65+
);
66+
67+
let non_nullable_dtype = DType::Primitive(seq.ptype(), Nullability::NonNullable);
68+
let min_scalar = Scalar::try_new(
69+
non_nullable_dtype.clone(),
70+
Some(ScalarValue::Primitive(min_pvalue)),
71+
)?;
72+
let max_scalar =
73+
Scalar::try_new(non_nullable_dtype, Some(ScalarValue::Primitive(max_pvalue)))?;
74+
75+
Ok(Some(Scalar::struct_(
76+
struct_dtype,
77+
vec![min_scalar, max_scalar],
78+
)))
79+
}
80+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use vortex_error::VortexResult;
5+
use vortex_mask::Mask;
6+
7+
use crate::ArrayRef;
8+
use crate::ExecutionCtx;
9+
use crate::aggregate_fn::AggregateFnRef;
10+
use crate::aggregate_fn::fns::min_max::MinMax;
11+
use crate::aggregate_fn::fns::min_max::make_struct_dtype;
12+
use crate::aggregate_fn::fns::min_max::min_max;
13+
use crate::aggregate_fn::kernels::DynAggregateKernel;
14+
use crate::arrays::Dict;
15+
use crate::scalar::Scalar;
16+
17+
/// Dict-specific min/max kernel.
18+
///
19+
/// When all dictionary values are referenced, min/max can be computed directly on the values
20+
/// array. Otherwise, unreferenced values are filtered out first.
21+
#[derive(Debug)]
22+
pub(crate) struct DictMinMaxKernel;
23+
24+
impl DynAggregateKernel for DictMinMaxKernel {
25+
fn aggregate(
26+
&self,
27+
aggregate_fn: &AggregateFnRef,
28+
batch: &ArrayRef,
29+
ctx: &mut ExecutionCtx,
30+
) -> VortexResult<Option<Scalar>> {
31+
if !aggregate_fn.is::<MinMax>() {
32+
return Ok(None);
33+
}
34+
35+
let Some(dict) = batch.as_opt::<Dict>() else {
36+
return Ok(None);
37+
};
38+
39+
let struct_dtype = make_struct_dtype(batch.dtype());
40+
41+
let result = if dict.has_all_values_referenced() {
42+
// All values are referenced, compute min/max directly on the values array.
43+
min_max(dict.values(), ctx)?
44+
} else {
45+
// Filter to only referenced values, then compute min/max.
46+
let referenced_mask = dict.compute_referenced_values_mask(true)?;
47+
let mask = Mask::from(referenced_mask);
48+
let filtered_values = dict.values().filter(mask)?;
49+
min_max(&filtered_values, ctx)?
50+
};
51+
52+
match result {
53+
Some(r) => Ok(Some(Scalar::struct_(struct_dtype, vec![r.min, r.max]))),
54+
None => Ok(Some(Scalar::null(struct_dtype))),
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)