vortex_array/aggregate_fn/
accumulator_grouped.rs1use arrow_buffer::ArrowNativeType;
5use vortex_buffer::Buffer;
6use vortex_error::VortexExpect;
7use vortex_error::VortexResult;
8use vortex_error::vortex_bail;
9use vortex_error::vortex_ensure;
10use vortex_error::vortex_err;
11use vortex_error::vortex_panic;
12use vortex_mask::Mask;
13
14use crate::ArrayRef;
15use crate::Canonical;
16use crate::Columnar;
17use crate::ExecutionCtx;
18use crate::IntoArray;
19use crate::aggregate_fn::Accumulator;
20use crate::aggregate_fn::AggregateFn;
21use crate::aggregate_fn::AggregateFnRef;
22use crate::aggregate_fn::AggregateFnVTable;
23use crate::aggregate_fn::DynAccumulator;
24use crate::aggregate_fn::session::AggregateFnSessionExt;
25use crate::arrays::ChunkedArray;
26use crate::arrays::FixedSizeListArray;
27use crate::arrays::ListViewArray;
28use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
29use crate::arrays::fixed_size_list::FixedSizeListArraySlotsExt;
30use crate::arrays::listview::ListViewArraySlotsExt;
31use crate::builders::builder_with_capacity_in;
32use crate::builtins::ArrayBuiltins;
33use crate::columnar::AnyColumnar;
34use crate::dtype::DType;
35use crate::executor::max_iterations;
36use crate::match_each_integer_ptype;
37
38pub type GroupedAccumulatorRef = Box<dyn DynGroupedAccumulator>;
40
41pub enum GroupedArray {
47 ListView(ListViewArray),
49 FixedSizeList(FixedSizeListArray),
51}
52
53impl From<ListViewArray> for GroupedArray {
54 fn from(groups: ListViewArray) -> Self {
55 Self::ListView(groups)
56 }
57}
58
59impl From<FixedSizeListArray> for GroupedArray {
60 fn from(groups: FixedSizeListArray) -> Self {
61 Self::FixedSizeList(groups)
62 }
63}
64
65impl GroupedArray {
66 pub fn elements(&self) -> &ArrayRef {
68 match self {
69 Self::ListView(groups) => groups.elements(),
70 Self::FixedSizeList(groups) => groups.elements(),
71 }
72 }
73
74 pub fn group_ranges(&self, ctx: &mut ExecutionCtx) -> VortexResult<GroupRanges> {
76 match self {
77 Self::ListView(groups) => list_view_group_ranges(groups, ctx),
78 Self::FixedSizeList(groups) => Ok(fixed_size_list_group_ranges(groups)),
79 }
80 }
81
82 pub fn group_validity(&self, ctx: &mut ExecutionCtx) -> VortexResult<Mask> {
84 match self {
85 Self::ListView(groups) => groups.validity()?.execute_mask(groups.len(), ctx),
86 Self::FixedSizeList(groups) => groups.validity()?.execute_mask(groups.len(), ctx),
87 }
88 }
89
90 pub fn len(&self) -> usize {
92 match self {
93 Self::ListView(groups) => groups.len(),
94 Self::FixedSizeList(groups) => groups.len(),
95 }
96 }
97
98 pub fn is_empty(&self) -> bool {
100 self.len() == 0
101 }
102
103 pub fn all_groups_valid(&self, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
105 Ok(self.group_validity(ctx)?.all_true())
106 }
107
108 unsafe fn with_elements_unchecked(&self, elements: ArrayRef) -> VortexResult<Self> {
109 Ok(match self {
110 Self::ListView(groups) => unsafe {
111 ListViewArray::new_unchecked(
112 elements,
113 groups.offsets().clone(),
114 groups.sizes().clone(),
115 groups.validity()?,
116 )
117 }
118 .into(),
119 Self::FixedSizeList(groups) => unsafe {
120 FixedSizeListArray::new_unchecked(
121 elements,
122 groups.list_size(),
123 groups.validity()?,
124 groups.len(),
125 )
126 }
127 .into(),
128 })
129 }
130}
131
132pub enum GroupRanges {
134 ListView {
136 ranges: Vec<(usize, usize)>,
138 },
139 FixedSizeList {
141 len: usize,
143 size: usize,
145 },
146}
147
148impl GroupRanges {
149 pub fn len(&self) -> usize {
151 match self {
152 Self::ListView { ranges } => ranges.len(),
153 Self::FixedSizeList { len, .. } => *len,
154 }
155 }
156
157 pub fn is_empty(&self) -> bool {
159 self.len() == 0
160 }
161
162 fn range(&self, index: usize) -> (usize, usize) {
164 match self {
165 Self::ListView { ranges } => ranges[index],
166 Self::FixedSizeList { len, size } => {
167 assert!(index < *len, "range index out of bounds");
168 (index * size, *size)
169 }
170 }
171 }
172
173 pub fn iter(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
175 (0..self.len()).map(|index| self.range(index))
176 }
177}
178
179pub struct GroupedAccumulator<V: AggregateFnVTable> {
184 vtable: V,
186 options: V::Options,
188 aggregate_fn: AggregateFnRef,
190 dtype: DType,
192 return_dtype: DType,
194 partial_dtype: DType,
196 partials: Vec<ArrayRef>,
198}
199
200impl<V: AggregateFnVTable> GroupedAccumulator<V> {
201 pub fn try_new(vtable: V, options: V::Options, dtype: DType) -> VortexResult<Self> {
202 let aggregate_fn = AggregateFn::new(vtable.clone(), options.clone()).erased();
203 let return_dtype = vtable.return_dtype(&options, &dtype).ok_or_else(|| {
204 vortex_err!(
205 "Aggregate function {} cannot be applied to dtype {}",
206 vtable.id(),
207 dtype
208 )
209 })?;
210 let partial_dtype = vtable.partial_dtype(&options, &dtype).ok_or_else(|| {
211 vortex_err!(
212 "Aggregate function {} cannot be applied to dtype {}",
213 vtable.id(),
214 dtype
215 )
216 })?;
217
218 Ok(Self {
219 vtable,
220 options,
221 aggregate_fn,
222 dtype,
223 return_dtype,
224 partial_dtype,
225 partials: vec![],
226 })
227 }
228}
229
230pub trait DynGroupedAccumulator: 'static + Send {
233 fn accumulate_list(&mut self, groups: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<()>;
235
236 fn flush(&mut self) -> VortexResult<ArrayRef>;
239
240 fn finish(&mut self) -> VortexResult<ArrayRef>;
243}
244
245impl<V: AggregateFnVTable> DynGroupedAccumulator for GroupedAccumulator<V> {
246 fn accumulate_list(&mut self, groups: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<()> {
247 let elements_dtype = match groups.dtype() {
248 DType::List(elem, _) => elem,
249 DType::FixedSizeList(elem, ..) => elem,
250 _ => vortex_bail!(
251 "Input DType mismatch: expected List or FixedSizeList, got {}",
252 groups.dtype()
253 ),
254 };
255 vortex_ensure!(
256 elements_dtype.as_ref() == &self.dtype,
257 "Input DType mismatch: expected {}, got {}",
258 self.dtype,
259 elements_dtype
260 );
261
262 let canonical = match groups.clone().execute::<Columnar>(ctx)? {
265 Columnar::Canonical(c) => c,
266 Columnar::Constant(c) => c.into_array().execute::<Canonical>(ctx)?,
267 };
268 match canonical {
269 Canonical::List(groups) => self.accumulate_grouped_array(groups.into(), ctx),
270 Canonical::FixedSizeList(groups) => self.accumulate_grouped_array(groups.into(), ctx),
271 _ => vortex_panic!("We checked the DType above, so this should never happen"),
272 }
273 }
274
275 fn flush(&mut self) -> VortexResult<ArrayRef> {
276 let mut states = std::mem::take(&mut self.partials);
277 if states.len() == 1 {
278 return Ok(states.pop().vortex_expect("checked one partial"));
279 }
280 Ok(ChunkedArray::try_new(states, self.partial_dtype.clone())?.into_array())
281 }
282
283 fn finish(&mut self) -> VortexResult<ArrayRef> {
284 let states = self.flush()?;
285 let results = self.vtable.finalize(states)?;
286
287 vortex_ensure!(
288 results.dtype() == &self.return_dtype,
289 "Return DType mismatch: expected {}, got {}",
290 self.return_dtype,
291 results.dtype()
292 );
293
294 Ok(results)
295 }
296}
297
298impl<V: AggregateFnVTable> GroupedAccumulator<V> {
299 fn accumulate_grouped_array(
300 &mut self,
301 groups: GroupedArray,
302 ctx: &mut ExecutionCtx,
303 ) -> VortexResult<()> {
304 let mut elements = groups.elements().clone();
305 let session = ctx.session().clone();
306
307 for _ in 0..max_iterations() {
308 if let Some(kernel) = session
310 .aggregate_fns()
311 .find_grouped_encoding_kernel(elements.encoding_id(), self.aggregate_fn.id())
312 {
313 let kernel_groups = unsafe { groups.with_elements_unchecked(elements.clone())? };
315 if let Some(result) =
316 kernel.grouped_aggregate(&self.aggregate_fn, &kernel_groups, ctx)?
317 {
318 return self.push_result(result);
319 }
320 }
321
322 if let Some(kernel) = session
324 .aggregate_fns()
325 .find_grouped_kernel(self.aggregate_fn.id())
326 {
327 let kernel_groups = unsafe { groups.with_elements_unchecked(elements.clone())? };
330 if let Some(result) =
331 kernel.grouped_aggregate(&self.aggregate_fn, &kernel_groups, ctx)?
332 {
333 return self.push_result(result);
334 }
335 }
336
337 if elements.is::<AnyColumnar>() {
338 break;
339 }
340
341 elements = elements.execute(ctx)?;
343 }
344
345 let elements = elements.execute::<Columnar>(ctx)?.into_array();
346 let grouped = unsafe { groups.with_elements_unchecked(elements)? };
349
350 self.accumulate_grouped_fallback(&grouped, ctx)
352 }
353
354 fn accumulate_grouped_fallback(
355 &mut self,
356 grouped: &GroupedArray,
357 ctx: &mut ExecutionCtx,
358 ) -> VortexResult<()> {
359 let mut accumulator = Accumulator::try_new(
360 self.vtable.clone(),
361 self.options.clone(),
362 self.dtype.clone(),
363 )?;
364 let mut states =
365 builder_with_capacity_in(&self.partial_dtype, grouped.len(), ctx.allocator());
366 let group_ranges = grouped.group_ranges(ctx)?;
367 let group_validity = grouped.group_validity(ctx)?;
368
369 for ((offset, size), valid) in group_ranges.iter().zip(group_validity.iter()) {
370 if valid {
371 let group = grouped.elements().slice(offset..offset + size)?;
372 accumulator.accumulate(&group, ctx)?;
373 states.append_scalar(&accumulator.flush()?)?;
374 } else {
375 states.append_null()
376 }
377 }
378
379 self.push_result(states.finish())
380 }
381
382 fn push_result(&mut self, state: ArrayRef) -> VortexResult<()> {
383 vortex_ensure!(
384 state.dtype() == &self.partial_dtype,
385 "State DType mismatch: expected {}, got {}",
386 self.partial_dtype,
387 state.dtype()
388 );
389 self.partials.push(state);
390 Ok(())
391 }
392}
393fn list_view_group_ranges(
394 groups: &ListViewArray,
395 ctx: &mut ExecutionCtx,
396) -> VortexResult<GroupRanges> {
397 let offsets = groups.offsets();
398 let sizes = groups.sizes().cast(offsets.dtype().clone())?;
399
400 let ranges = match_each_integer_ptype!(offsets.dtype().as_ptype(), |O| {
401 let offsets = offsets.clone().execute::<Buffer<O>>(ctx)?;
402 let sizes = sizes.execute::<Buffer<O>>(ctx)?;
403 offsets
404 .as_ref()
405 .iter()
406 .zip(sizes.as_ref().iter())
407 .map(|(offset, size)| {
408 (
409 offset.to_usize().vortex_expect("Offset value is not usize"),
410 size.to_usize().vortex_expect("Size value is not usize"),
411 )
412 })
413 .collect::<Vec<_>>()
414 });
415
416 Ok(GroupRanges::ListView { ranges })
417}
418
419fn fixed_size_list_group_ranges(groups: &FixedSizeListArray) -> GroupRanges {
420 GroupRanges::FixedSizeList {
421 len: groups.len(),
422 size: groups.list_size() as usize,
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use vortex_error::VortexResult;
429
430 use crate::ArrayRef;
431 use crate::IntoArray;
432 use crate::aggregate_fn::DynGroupedAccumulator;
433 use crate::aggregate_fn::GroupedAccumulator;
434 use crate::aggregate_fn::NumericalAggregateOpts;
435 use crate::aggregate_fn::fns::count::Count;
436 use crate::arrays::Chunked;
437 use crate::arrays::PrimitiveArray;
438 use crate::dtype::DType;
439 use crate::dtype::Nullability::NonNullable;
440 use crate::dtype::PType;
441
442 fn accumulator() -> VortexResult<GroupedAccumulator<Count>> {
443 GroupedAccumulator::try_new(
444 Count,
445 NumericalAggregateOpts::default(),
446 DType::Primitive(PType::I32, NonNullable),
447 )
448 }
449
450 fn state(values: impl IntoIterator<Item = u64>) -> ArrayRef {
451 PrimitiveArray::from_iter(values).into_array()
452 }
453
454 #[test]
455 fn test_flush_single_partial_returns_original_array() -> VortexResult<()> {
456 let mut accumulator = accumulator()?;
457 let state = state([1, 2, 3]);
458 accumulator.push_result(state.clone())?;
459
460 let flushed = accumulator.flush()?;
461
462 assert!(ArrayRef::ptr_eq(&flushed, &state));
463 Ok(())
464 }
465
466 #[test]
467 fn test_flush_multiple_partials_returns_chunked_array() -> VortexResult<()> {
468 let mut accumulator = accumulator()?;
469 accumulator.push_result(state([1, 2]))?;
470 accumulator.push_result(state([3, 4]))?;
471
472 let flushed = accumulator.flush()?;
473
474 assert!(flushed.is::<Chunked>());
475 assert_eq!(flushed.len(), 4);
476 Ok(())
477 }
478
479 #[test]
480 fn test_flush_without_partials_returns_empty_chunked_array() -> VortexResult<()> {
481 let mut accumulator = accumulator()?;
482
483 let flushed = accumulator.flush()?;
484
485 assert!(flushed.is::<Chunked>());
486 assert!(flushed.is_empty());
487 Ok(())
488 }
489}