Skip to main content

vortex_sequence/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7use std::hash::Hasher;
8
9use num_traits::cast::FromPrimitive;
10use prost::Message;
11use smallvec::smallvec;
12use vortex_array::Array;
13use vortex_array::ArrayEq;
14use vortex_array::ArrayHash;
15use vortex_array::ArrayId;
16use vortex_array::ArrayParts;
17use vortex_array::ArrayRef;
18use vortex_array::ArrayView;
19use vortex_array::EqMode;
20use vortex_array::ExecutionCtx;
21use vortex_array::ExecutionResult;
22use vortex_array::buffer::BufferHandle;
23use vortex_array::dtype::DType;
24use vortex_array::dtype::NativePType;
25use vortex_array::dtype::Nullability;
26use vortex_array::dtype::Nullability::NonNullable;
27use vortex_array::dtype::PType;
28use vortex_array::expr::stats::Precision as StatPrecision;
29use vortex_array::expr::stats::Stat;
30use vortex_array::match_each_integer_ptype;
31use vortex_array::match_each_native_ptype;
32use vortex_array::match_each_pvalue;
33use vortex_array::scalar::PValue;
34use vortex_array::scalar::Scalar;
35use vortex_array::scalar::ScalarValue;
36use vortex_array::serde::ArrayChildren;
37use vortex_array::stats::StatsSet;
38use vortex_array::validity::Validity;
39use vortex_array::vtable::OperationsVTable;
40use vortex_array::vtable::VTable;
41use vortex_array::vtable::ValidityVTable;
42use vortex_error::VortexExpect;
43use vortex_error::VortexResult;
44use vortex_error::vortex_bail;
45use vortex_error::vortex_ensure;
46use vortex_error::vortex_err;
47use vortex_error::vortex_panic;
48use vortex_session::VortexSession;
49use vortex_session::registry::CachedId;
50
51use crate::compress::sequence_decompress;
52use crate::rules::RULES;
53
54/// A [`Sequence`]-encoded Vortex array.
55pub type SequenceArray = Array<Sequence>;
56
57#[derive(Clone, prost::Message)]
58pub struct SequenceMetadata {
59    #[prost(message, tag = "1")]
60    base: Option<vortex_proto::scalar::ScalarValue>,
61    #[prost(message, tag = "2")]
62    multiplier: Option<vortex_proto::scalar::ScalarValue>,
63}
64
65pub(super) const SLOT_NAMES: [&str; 0] = [];
66
67#[derive(Clone, Debug)]
68/// An array representing the equation `A[i] = base + i * multiplier`.
69pub struct SequenceData {
70    base: PValue,
71    multiplier: PValue,
72}
73
74impl Display for SequenceData {
75    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
76        write!(f, "base: {}, multiplier: {}", self.base, self.multiplier)
77    }
78}
79
80pub struct SequenceDataParts {
81    pub base: PValue,
82    pub multiplier: PValue,
83    pub ptype: PType,
84}
85
86impl SequenceData {
87    pub(crate) fn try_new_typed<T: NativePType + Into<PValue>>(
88        base: T,
89        multiplier: T,
90        nullability: Nullability,
91        length: usize,
92    ) -> VortexResult<Self> {
93        Self::try_new(
94            base.into(),
95            multiplier.into(),
96            T::PTYPE,
97            nullability,
98            length,
99        )
100    }
101
102    /// Constructs a sequence array using two integer values (with the same ptype).
103    pub(crate) fn try_new(
104        base: PValue,
105        multiplier: PValue,
106        ptype: PType,
107        nullability: Nullability,
108        length: usize,
109    ) -> VortexResult<Self> {
110        let dtype = DType::Primitive(ptype, nullability);
111        Self::validate(base, multiplier, &dtype, length)?;
112        let (base, multiplier) = Self::normalize(base, multiplier, ptype)?;
113
114        Ok(unsafe { Self::new_unchecked(base, multiplier) })
115    }
116
117    pub fn validate(
118        base: PValue,
119        multiplier: PValue,
120        dtype: &DType,
121        length: usize,
122    ) -> VortexResult<()> {
123        let DType::Primitive(ptype, _) = dtype else {
124            vortex_bail!("only primitive dtypes are supported in SequenceArray currently");
125        };
126
127        if !ptype.is_int() {
128            vortex_bail!("only integer ptype are supported in SequenceArray currently")
129        }
130
131        vortex_ensure!(length > 0, "SequenceArray length must be greater than zero");
132        Self::try_last(base, multiplier, *ptype, length).map_err(|e| {
133            e.with_context(format!(
134                "final value not expressible, base = {base:?}, multiplier = {multiplier:?}, len = {length} ",
135            ))
136        })?;
137
138        Ok(())
139    }
140
141    fn normalize(base: PValue, multiplier: PValue, ptype: PType) -> VortexResult<(PValue, PValue)> {
142        match_each_integer_ptype!(ptype, |P| {
143            Ok((
144                PValue::from(base.cast::<P>()?),
145                PValue::from(multiplier.cast::<P>()?),
146            ))
147        })
148    }
149
150    /// Constructs a [`SequenceArray`] payload without validation.
151    ///
152    /// # Safety
153    ///
154    /// The caller must ensure that:
155    /// - `base` and `multiplier` are both normalized to the same integer `ptype`.
156    /// - they are logically compatible with the outer dtype and len.
157    pub(crate) unsafe fn new_unchecked(base: PValue, multiplier: PValue) -> Self {
158        Self { base, multiplier }
159    }
160
161    pub fn ptype(&self) -> PType {
162        self.base.ptype()
163    }
164
165    pub fn base(&self) -> PValue {
166        self.base
167    }
168
169    pub fn multiplier(&self) -> PValue {
170        self.multiplier
171    }
172
173    pub fn into_parts(self) -> SequenceDataParts {
174        SequenceDataParts {
175            base: self.base,
176            multiplier: self.multiplier,
177            ptype: self.base.ptype(),
178        }
179    }
180
181    pub(crate) fn try_last(
182        base: PValue,
183        multiplier: PValue,
184        ptype: PType,
185        length: usize,
186    ) -> VortexResult<PValue> {
187        match_each_integer_ptype!(ptype, |P| {
188            let len_t = <P>::from_usize(length - 1)
189                .ok_or_else(|| vortex_err!("cannot convert length {} into {}", length, ptype))?;
190
191            let base = base.cast::<P>()?;
192            let multiplier = multiplier.cast::<P>()?;
193            let last = len_t
194                .checked_mul(multiplier)
195                .and_then(|offset| offset.checked_add(base))
196                .ok_or_else(|| vortex_err!("last value computation overflows"))?;
197            Ok(PValue::from(last))
198        })
199    }
200
201    pub(crate) fn index_value(&self, idx: usize) -> PValue {
202        match_each_native_ptype!(self.ptype(), |P| {
203            let base = self.base.cast::<P>().vortex_expect("must be able to cast");
204            let multiplier = self
205                .multiplier
206                .cast::<P>()
207                .vortex_expect("must be able to cast");
208            let value = base + (multiplier * <P>::from_usize(idx).vortex_expect("must fit"));
209
210            PValue::from(value)
211        })
212    }
213}
214
215impl ArrayHash for SequenceData {
216    fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
217        self.base.hash(state);
218        self.multiplier.hash(state);
219    }
220}
221
222impl ArrayEq for SequenceData {
223    fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
224        self.base == other.base && self.multiplier == other.multiplier
225    }
226}
227
228impl VTable for Sequence {
229    type TypedArrayData = SequenceData;
230
231    type OperationsVTable = Self;
232    type ValidityVTable = Self;
233
234    fn id(&self) -> ArrayId {
235        static ID: CachedId = CachedId::new("vortex.sequence");
236        *ID
237    }
238
239    fn validate(
240        &self,
241        data: &Self::TypedArrayData,
242        dtype: &DType,
243        len: usize,
244        _slots: &[Option<ArrayRef>],
245    ) -> VortexResult<()> {
246        SequenceData::validate(data.base, data.multiplier, dtype, len)
247    }
248
249    fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
250        0
251    }
252
253    fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
254        vortex_panic!("SequenceArray buffer index {idx} out of bounds")
255    }
256
257    fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
258        vortex_panic!("SequenceArray buffer_name index {idx} out of bounds")
259    }
260
261    fn with_buffers(
262        &self,
263        array: ArrayView<'_, Self>,
264        buffers: &[BufferHandle],
265    ) -> VortexResult<ArrayParts<Self>> {
266        vortex_array::vtable::with_empty_buffers(self, array, buffers)
267    }
268
269    fn serialize(
270        array: ArrayView<'_, Self>,
271        _session: &VortexSession,
272    ) -> VortexResult<Option<Vec<u8>>> {
273        let metadata = SequenceMetadata {
274            base: Some((&array.base()).into()),
275            multiplier: Some((&array.multiplier()).into()),
276        };
277
278        Ok(Some(metadata.encode_to_vec()))
279    }
280
281    fn deserialize(
282        &self,
283        dtype: &DType,
284        len: usize,
285        metadata: &[u8],
286        buffers: &[BufferHandle],
287        children: &dyn ArrayChildren,
288        session: &VortexSession,
289    ) -> VortexResult<ArrayParts<Self>> {
290        vortex_ensure!(
291            buffers.is_empty(),
292            "SequenceArray expects 0 buffers, got {}",
293            buffers.len()
294        );
295        vortex_ensure!(
296            children.is_empty(),
297            "SequenceArray expects 0 children, got {}",
298            children.len()
299        );
300        let metadata = SequenceMetadata::decode(metadata)?;
301
302        let ptype = dtype.as_ptype();
303
304        // We go via Scalar to validate that the value is valid for the ptype.
305        let base = Scalar::from_proto_value(
306            metadata
307                .base
308                .as_ref()
309                .ok_or_else(|| vortex_err!("base required"))?,
310            &DType::Primitive(ptype, NonNullable),
311            session,
312        )?
313        .as_primitive()
314        .pvalue()
315        .vortex_expect("sequence array base should be a non-nullable primitive");
316
317        let multiplier = Scalar::from_proto_value(
318            metadata
319                .multiplier
320                .as_ref()
321                .ok_or_else(|| vortex_err!("multiplier required"))?,
322            &DType::Primitive(ptype, NonNullable),
323            session,
324        )?
325        .as_primitive()
326        .pvalue()
327        .vortex_expect("sequence array multiplier should be a non-nullable primitive");
328
329        let data = SequenceData::try_new(base, multiplier, ptype, dtype.nullability(), len)?;
330        Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data))
331    }
332
333    fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
334        SLOT_NAMES[idx].to_string()
335    }
336
337    fn execute(array: Array<Self>, _ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
338        sequence_decompress(&array).map(ExecutionResult::done)
339    }
340
341    fn reduce_parent(
342        array: ArrayView<'_, Self>,
343        parent: &ArrayRef,
344        child_idx: usize,
345    ) -> VortexResult<Option<ArrayRef>> {
346        RULES.evaluate(array, parent, child_idx)
347    }
348}
349
350impl OperationsVTable<Sequence> for Sequence {
351    fn scalar_at(
352        array: ArrayView<'_, Sequence>,
353        index: usize,
354        _ctx: &mut ExecutionCtx,
355    ) -> VortexResult<Scalar> {
356        Scalar::try_new(
357            array.dtype().clone(),
358            Some(ScalarValue::Primitive(array.index_value(index))),
359        )
360    }
361}
362
363impl ValidityVTable<Sequence> for Sequence {
364    fn validity(_array: ArrayView<'_, Sequence>) -> VortexResult<Validity> {
365        Ok(Validity::AllValid)
366    }
367}
368
369#[derive(Clone, Debug)]
370pub struct Sequence;
371
372impl Sequence {
373    fn stats(multiplier: PValue) -> StatsSet {
374        // A sequence A[i] = base + i * multiplier is sorted iff multiplier >= 0,
375        // and strictly sorted iff multiplier > 0.
376        let (is_sorted, is_strict_sorted) = match_each_pvalue!(
377            multiplier,
378            uint: |v| { (true, v > 0) },
379            int: |v| { (v >= 0, v > 0) },
380            float: |_v| { unreachable!("float multiplier not supported") }
381        );
382
383        // SAFETY: we don't have duplicate stats.
384        unsafe {
385            StatsSet::new_unchecked(smallvec![
386                (Stat::IsSorted, StatPrecision::Exact(is_sorted.into())),
387                (
388                    Stat::IsStrictSorted,
389                    StatPrecision::Exact(is_strict_sorted.into()),
390                ),
391            ])
392        }
393    }
394
395    /// Construct a new [`SequenceArray`] from pre-validated parts.
396    ///
397    /// # Safety
398    ///
399    /// Caller must ensure the sequence is logically compatible with the provided dtype and len.
400    pub(crate) unsafe fn new_unchecked(
401        base: PValue,
402        multiplier: PValue,
403        ptype: PType,
404        nullability: Nullability,
405        length: usize,
406    ) -> SequenceArray {
407        let dtype = DType::Primitive(ptype, nullability);
408        let (base, multiplier) = SequenceData::normalize(base, multiplier, ptype)
409            .vortex_expect("SequenceArray parts must be normalized to the target ptype");
410        let stats = Self::stats(multiplier);
411        let data = unsafe { SequenceData::new_unchecked(base, multiplier) };
412        unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
413            .with_stats_set(stats)
414    }
415
416    /// Construct a new [`SequenceArray`] from its components.
417    pub fn try_new(
418        base: PValue,
419        multiplier: PValue,
420        ptype: PType,
421        nullability: Nullability,
422        length: usize,
423    ) -> VortexResult<SequenceArray> {
424        let dtype = DType::Primitive(ptype, nullability);
425        let data = SequenceData::try_new(base, multiplier, ptype, nullability, length)?;
426        let stats = Self::stats(data.multiplier());
427        Ok(
428            unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
429                .with_stats_set(stats),
430        )
431    }
432
433    /// Construct a new typed [`SequenceArray`] from base/multiplier values.
434    pub fn try_new_typed<T: NativePType + Into<PValue>>(
435        base: T,
436        multiplier: T,
437        nullability: Nullability,
438        length: usize,
439    ) -> VortexResult<SequenceArray> {
440        let ptype = T::PTYPE;
441        let dtype = DType::Primitive(ptype, nullability);
442        let data = SequenceData::try_new_typed(base, multiplier, nullability, length)?;
443        let stats = Self::stats(data.multiplier());
444        Ok(
445            unsafe { Array::from_parts_unchecked(ArrayParts::new(Sequence, dtype, length, data)) }
446                .with_stats_set(stats),
447        )
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use std::sync::LazyLock;
454
455    use vortex_array::VortexSessionExecute;
456    use vortex_array::arrays::PrimitiveArray;
457    use vortex_array::assert_arrays_eq;
458    use vortex_array::dtype::Nullability;
459    use vortex_array::expr::stats::Precision as StatPrecision;
460    use vortex_array::expr::stats::Stat;
461    use vortex_array::expr::stats::StatsProviderExt;
462    use vortex_array::scalar::Scalar;
463    use vortex_array::scalar::ScalarValue;
464    use vortex_error::VortexResult;
465    use vortex_session::VortexSession;
466
467    use crate::Sequence;
468
469    static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
470        let session = vortex_array::array_session();
471        crate::initialize(&session);
472        session
473    });
474
475    #[test]
476    fn test_sequence_canonical() {
477        let arr = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4).unwrap();
478
479        let canon = PrimitiveArray::from_iter((0..4).map(|i| 2i64 + i * 3));
480
481        assert_arrays_eq!(arr, canon, &mut SESSION.create_execution_ctx());
482    }
483
484    #[test]
485    fn test_sequence_slice_canonical() {
486        let arr = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4)
487            .unwrap()
488            .slice(2..3)
489            .unwrap();
490
491        let canon = PrimitiveArray::from_iter((2..3).map(|i| 2i64 + i * 3));
492
493        assert_arrays_eq!(arr, canon, &mut SESSION.create_execution_ctx());
494    }
495
496    #[test]
497    fn test_sequence_scalar_at() {
498        let scalar = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4)
499            .unwrap()
500            .execute_scalar(2, &mut SESSION.create_execution_ctx())
501            .unwrap();
502
503        assert_eq!(
504            scalar,
505            Scalar::try_new(scalar.dtype().clone(), Some(ScalarValue::from(8i64))).unwrap()
506        )
507    }
508
509    #[test]
510    fn test_sequence_min_max() {
511        assert!(Sequence::try_new_typed(-127i8, -1i8, Nullability::NonNullable, 2).is_ok());
512        assert!(Sequence::try_new_typed(126i8, -1i8, Nullability::NonNullable, 2).is_ok());
513    }
514
515    #[test]
516    fn test_sequence_too_big() {
517        assert!(Sequence::try_new_typed(127i8, 1i8, Nullability::NonNullable, 2).is_err());
518        assert!(Sequence::try_new_typed(-128i8, -1i8, Nullability::NonNullable, 2).is_err());
519    }
520
521    #[test]
522    fn positive_multiplier_is_strict_sorted() -> VortexResult<()> {
523        let arr = Sequence::try_new_typed(0i64, 3, Nullability::NonNullable, 4)?;
524
525        let is_sorted = arr
526            .statistics()
527            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
528        assert_eq!(is_sorted, StatPrecision::Exact(true));
529
530        let is_strict_sorted = arr
531            .statistics()
532            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
533        assert_eq!(is_strict_sorted, StatPrecision::Exact(true));
534        Ok(())
535    }
536
537    #[test]
538    fn zero_multiplier_is_sorted_not_strict() -> VortexResult<()> {
539        let arr = Sequence::try_new_typed(5i64, 0, Nullability::NonNullable, 4)?;
540
541        let is_sorted = arr
542            .statistics()
543            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
544        assert_eq!(is_sorted, StatPrecision::Exact(true));
545
546        let is_strict_sorted = arr
547            .statistics()
548            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
549        assert_eq!(is_strict_sorted, StatPrecision::Exact(false));
550        Ok(())
551    }
552
553    #[test]
554    fn negative_multiplier_not_sorted() -> VortexResult<()> {
555        let arr = Sequence::try_new_typed(10i64, -1, Nullability::NonNullable, 4)?;
556
557        let is_sorted = arr
558            .statistics()
559            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
560        assert_eq!(is_sorted, StatPrecision::Exact(false));
561
562        let is_strict_sorted = arr
563            .statistics()
564            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
565        assert_eq!(is_strict_sorted, StatPrecision::Exact(false));
566        Ok(())
567    }
568
569    // This is regression test for an issue caught by the fuzzer, where SequenceArrays with
570    // multiplier > i64::MAX were unable to be constructed.
571    #[test]
572    fn test_large_multiplier_sorted() -> VortexResult<()> {
573        let large_multiplier = (i64::MAX as u64) + 1;
574        let arr = Sequence::try_new_typed(0, large_multiplier, Nullability::NonNullable, 2)?;
575
576        let is_sorted = arr
577            .statistics()
578            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
579
580        let is_strict_sorted = arr
581            .statistics()
582            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsStrictSorted));
583
584        assert_eq!(is_sorted, StatPrecision::Exact(true));
585        assert_eq!(is_strict_sorted, StatPrecision::Exact(true));
586
587        Ok(())
588    }
589}