Skip to main content

runlength_arrow/
batch.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use arrow_array::{
5    Array, ArrayRef, Int64Array, RecordBatch, RecordBatchOptions, UInt64Array, new_empty_array,
6};
7use arrow_row::{RowConverter, Rows, SortField};
8use arrow_schema::{ArrowError, DataType, Field, Schema};
9use arrow_select::concat::concat;
10use arrow_select::interleave::interleave;
11use arrow_select::take::take;
12use rayon::prelude::*;
13use runlength::How;
14
15use crate::error::{Error, Result};
16use crate::kernels::Kernel;
17use crate::rle::{ArrowRle, default_fill};
18
19/// Many Arrow-backed sequences, each identified by a row of a key table.
20///
21/// The keys are a [`RecordBatch`] with any number of columns of any types
22/// (for example a chromosome and a strand column), one row per sequence.
23/// Sequences are stored back to back: sequence `i` is runs
24/// `offsets[i]..offsets[i + 1]` of `ends` and `values`, with ends restarting
25/// at 0. Batch operations run in parallel across sequences.
26#[derive(Clone, Debug)]
27pub struct ArrowRleBatch {
28    keys: RecordBatch,
29    offsets: Vec<usize>,
30    ends: Vec<i64>,
31    values: ArrayRef,
32}
33
34/// A key table with one column.
35pub(crate) fn single_key_column(name: &str, column: ArrayRef) -> RecordBatch {
36    let schema = Schema::new(vec![Field::new(
37        name,
38        column.data_type().clone(),
39        column.is_nullable(),
40    )]);
41    RecordBatch::try_new(Arc::new(schema), vec![column]).expect("one column matches its own schema")
42}
43
44/// Encodes key rows so they can be hashed and compared across batches.
45pub(crate) fn key_rows(converter: &RowConverter, keys: &RecordBatch) -> Result<Rows> {
46    Ok(converter.convert_columns(keys.columns())?)
47}
48
49pub(crate) fn key_converter(keys: &RecordBatch) -> Result<RowConverter> {
50    let fields = keys
51        .schema()
52        .fields()
53        .iter()
54        .map(|f| SortField::new(f.data_type().clone()))
55        .collect();
56    Ok(RowConverter::new(fields)?)
57}
58
59fn concat_values(rles: &[ArrowRle], data_type: &DataType) -> Result<ArrayRef> {
60    if rles.is_empty() {
61        return Ok(new_empty_array(data_type));
62    }
63    let arrays: Vec<&dyn Array> = rles.iter().map(|r| r.values.as_ref()).collect();
64    Ok(concat(&arrays)?)
65}
66
67impl ArrowRleBatch {
68    /// Builds a batch from its flat parts, validating them. Keys must be
69    /// unique.
70    pub fn try_new(
71        keys: RecordBatch,
72        offsets: Vec<usize>,
73        ends: Vec<i64>,
74        values: ArrayRef,
75    ) -> Result<Self> {
76        let n = keys.num_rows();
77        if offsets.len() != n + 1 {
78            return Err(runlength::Error::LengthMismatch {
79                what: "offsets and keys + 1",
80                left: offsets.len(),
81                right: n + 1,
82            }
83            .into());
84        }
85        if ends.len() != values.len() {
86            return Err(runlength::Error::LengthMismatch {
87                what: "ends and values",
88                left: ends.len(),
89                right: values.len(),
90            }
91            .into());
92        }
93        if offsets[0] != 0 || offsets[n] != ends.len() || offsets.windows(2).any(|w| w[0] > w[1]) {
94            return Err(runlength::Error::InvalidOffsets.into());
95        }
96        for (position, w) in offsets.windows(2).enumerate() {
97            let rle_ends = ends[w[0]..w[1]].to_vec();
98            let values = values.slice(w[0], w[1] - w[0]);
99            ArrowRle::try_new(rle_ends, values).map_err(|e| e.in_key(position))?;
100        }
101        let converter = key_converter(&keys)?;
102        let rows = key_rows(&converter, &keys)?;
103        let mut seen = HashSet::with_capacity(n);
104        for (position, row) in rows.iter().enumerate() {
105            if !seen.insert(row) {
106                return Err(runlength::Error::DuplicateKey { position }.into());
107            }
108        }
109        Ok(ArrowRleBatch {
110            keys,
111            offsets,
112            ends,
113            values,
114        })
115    }
116
117    pub(crate) fn from_parts_unchecked(
118        keys: RecordBatch,
119        offsets: Vec<usize>,
120        ends: Vec<i64>,
121        values: ArrayRef,
122    ) -> Result<Self> {
123        debug_assert_eq!(offsets.len(), keys.num_rows() + 1);
124        Ok(ArrowRleBatch {
125            keys,
126            offsets,
127            ends,
128            values,
129        })
130    }
131
132    /// Builds a batch from one key row and one sequence per entry. All
133    /// sequences must have the same value type, `data_type`.
134    pub fn from_rles(keys: RecordBatch, rles: Vec<ArrowRle>, data_type: &DataType) -> Result<Self> {
135        if keys.num_rows() != rles.len() {
136            return Err(runlength::Error::LengthMismatch {
137                what: "keys and sequences",
138                left: keys.num_rows(),
139                right: rles.len(),
140            }
141            .into());
142        }
143        let mut offsets = Vec::with_capacity(rles.len() + 1);
144        offsets.push(0);
145        let mut ends = Vec::new();
146        for rle in &rles {
147            ends.extend_from_slice(&rle.ends);
148            offsets.push(ends.len());
149        }
150        let values = concat_values(&rles, data_type)?;
151        Self::try_new(keys, offsets, ends, values)
152    }
153
154    /// The number of sequences.
155    pub fn len(&self) -> usize {
156        self.keys.num_rows()
157    }
158
159    pub fn is_empty(&self) -> bool {
160        self.len() == 0
161    }
162
163    /// The key table, one row per sequence.
164    pub fn keys(&self) -> &RecordBatch {
165        &self.keys
166    }
167
168    pub fn offsets(&self) -> &[usize] {
169        &self.offsets
170    }
171
172    /// The run ends of all sequences, back to back.
173    pub fn ends(&self) -> &[i64] {
174        &self.ends
175    }
176
177    /// The run values of all sequences, back to back.
178    pub fn values(&self) -> &ArrayRef {
179        &self.values
180    }
181
182    /// The sequence at `position`. The values are a zero-copy slice.
183    pub fn get_index(&self, position: usize) -> Option<ArrowRle> {
184        (position < self.len()).then(|| self.rle_at(position))
185    }
186
187    fn rle_at(&self, position: usize) -> ArrowRle {
188        let (start, end) = (self.offsets[position], self.offsets[position + 1]);
189        ArrowRle {
190            ends: self.ends[start..end].to_vec(),
191            values: self.values.slice(start, end - start),
192        }
193    }
194
195    /// The sequences in key order.
196    pub fn iter(&self) -> impl Iterator<Item = ArrowRle> + '_ {
197        (0..self.len()).map(|i| self.rle_at(i))
198    }
199
200    /// The position of each row of `keys` in this batch, or `None` if absent.
201    /// `keys` must have the same column types as this batch's keys.
202    pub fn positions(&self, keys: &RecordBatch) -> Result<Vec<Option<usize>>> {
203        self.check_key_schema(keys)?;
204        let converter = key_converter(&self.keys)?;
205        let mine = key_rows(&converter, &self.keys)?;
206        let index: HashMap<_, _> = mine.iter().enumerate().map(|(i, row)| (row, i)).collect();
207        let theirs = key_rows(&converter, keys)?;
208        Ok(theirs.iter().map(|row| index.get(&row).copied()).collect())
209    }
210
211    pub(crate) fn check_key_schema(&self, keys: &RecordBatch) -> Result<()> {
212        let (mine, theirs) = (self.keys.schema(), keys.schema());
213        let same = mine.fields().len() == theirs.fields().len()
214            && mine
215                .fields()
216                .iter()
217                .zip(theirs.fields())
218                .all(|(a, b)| a.name() == b.name() && a.data_type() == b.data_type());
219        if same {
220            Ok(())
221        } else {
222            Err(Error::KeySchemaMismatch)
223        }
224    }
225
226    /// The output keys of a join, as (side, row) pairs for `interleave`, and
227    /// each key's position on both sides.
228    #[allow(clippy::type_complexity)]
229    fn join(
230        &self,
231        other: &ArrowRleBatch,
232        how: How,
233    ) -> Result<(Vec<(usize, usize)>, Vec<(Option<usize>, Option<usize>)>)> {
234        self.check_key_schema(&other.keys)?;
235        let converter = key_converter(&self.keys)?;
236        let (rows_a, rows_b) = (
237            key_rows(&converter, &self.keys)?,
238            key_rows(&converter, &other.keys)?,
239        );
240        let index_a: HashMap<_, _> = rows_a.iter().enumerate().map(|(i, row)| (row, i)).collect();
241        let index_b: HashMap<_, _> = rows_b.iter().enumerate().map(|(j, row)| (row, j)).collect();
242
243        let (mut sources, mut pairs) = (Vec::new(), Vec::new());
244        for (i, row) in rows_a.iter().enumerate() {
245            let j = index_b.get(&row).copied();
246            if j.is_some() || matches!(how, How::Outer | How::Left) {
247                sources.push((0, i));
248                pairs.push((Some(i), j));
249            }
250        }
251        if matches!(how, How::Outer | How::Right) {
252            for (j, row) in rows_b.iter().enumerate() {
253                if !index_a.contains_key(&row) {
254                    sources.push((1, j));
255                    pairs.push((None, Some(j)));
256                }
257            }
258        }
259        Ok((sources, pairs))
260    }
261
262    /// Combines two batches key by key, in parallel, merging each result.
263    ///
264    /// Both key tables must have the same column names and types. `how` picks
265    /// the keys, which appear in the left batch's order followed by keys only
266    /// in the right batch. A key missing on one side is an empty sequence
267    /// there, padded with that side's fill value. See
268    /// [`ArrowRle::zip_with`] for `f`.
269    pub fn zip_with<F>(
270        &self,
271        other: &ArrowRleBatch,
272        how: How,
273        fill_a: Option<&dyn Array>,
274        fill_b: Option<&dyn Array>,
275        f: F,
276    ) -> Result<ArrowRleBatch>
277    where
278        F: Fn(&dyn Array, &dyn Array) -> std::result::Result<ArrayRef, ArrowError> + Sync,
279    {
280        let (sources, pairs) = self.join(other, how)?;
281        let rles = pairs
282            .par_iter()
283            .enumerate()
284            .map(|(position, &(i, j))| {
285                let a = i.map_or_else(
286                    || ArrowRle::empty(self.values.data_type()),
287                    |i| self.rle_at(i),
288                );
289                let b = j.map_or_else(
290                    || ArrowRle::empty(other.values.data_type()),
291                    |j| other.rle_at(j),
292                );
293                a.zip_with(&b, fill_a, fill_b, &f)
294                    .map_err(|e| e.in_key(position))
295            })
296            .collect::<Result<Vec<_>>>()?;
297
298        let columns = (0..self.keys.num_columns())
299            .map(|c| {
300                interleave(
301                    &[self.keys.column(c).as_ref(), other.keys.column(c).as_ref()],
302                    &sources,
303                )
304            })
305            .collect::<std::result::Result<Vec<_>, _>>()?;
306        let keys = RecordBatch::try_new_with_options(
307            self.keys.schema(),
308            columns,
309            &RecordBatchOptions::new().with_row_count(Some(sources.len())),
310        )?;
311        let data_type = rles
312            .first()
313            .map_or(DataType::Null, |r| r.data_type().clone());
314        let mut offsets = Vec::with_capacity(rles.len() + 1);
315        offsets.push(0);
316        let mut ends = Vec::new();
317        for rle in &rles {
318            ends.extend_from_slice(&rle.ends);
319            offsets.push(ends.len());
320        }
321        let values = concat_values(&rles, &data_type)?;
322        Self::from_parts_unchecked(keys, offsets, ends, values)
323    }
324
325    /// The batch as one long table: the key columns, then `start`, `end` and
326    /// `value`, with one row per run. This is the natural shape for polars or
327    /// pandas.
328    pub fn to_record_batch(&self) -> Result<RecordBatch> {
329        let mut key_index = Vec::with_capacity(self.ends.len());
330        let mut starts = Vec::with_capacity(self.ends.len());
331        for (k, w) in self.offsets.windows(2).enumerate() {
332            let mut prev = 0;
333            for &end in &self.ends[w[0]..w[1]] {
334                key_index.push(k as u64);
335                starts.push(prev);
336                prev = end;
337            }
338        }
339        let key_index = UInt64Array::from(key_index);
340        let mut fields: Vec<Field> = self
341            .keys
342            .schema()
343            .fields()
344            .iter()
345            .map(|f| f.as_ref().clone())
346            .collect();
347        let mut columns = self
348            .keys
349            .columns()
350            .iter()
351            .map(|c| take(c.as_ref(), &key_index, None))
352            .collect::<std::result::Result<Vec<_>, _>>()?;
353        fields.push(Field::new("start", DataType::Int64, false));
354        fields.push(Field::new("end", DataType::Int64, false));
355        fields.push(Field::new("value", self.values.data_type().clone(), true));
356        columns.push(Arc::new(Int64Array::from(starts)));
357        columns.push(Arc::new(Int64Array::from(self.ends.clone())));
358        columns.push(self.values.clone());
359        Ok(RecordBatch::try_new_with_options(
360            Arc::new(Schema::new(fields)),
361            columns,
362            &RecordBatchOptions::new().with_row_count(Some(self.ends.len())),
363        )?)
364    }
365
366    /// Combines with `other` by an outer join, using [`default_fill`] on each
367    /// side, and merges.
368    pub fn zip_with_default(&self, other: &ArrowRleBatch, f: Kernel) -> Result<ArrowRleBatch> {
369        self.op(other, f)
370    }
371
372    fn op(&self, other: &ArrowRleBatch, f: Kernel) -> Result<ArrowRleBatch> {
373        let fill_a = default_fill(self.values.data_type());
374        let fill_b = default_fill(other.values.data_type());
375        self.zip_with(other, How::Outer, fill_a.as_deref(), fill_b.as_deref(), f)
376    }
377}
378
379crate::kernels::kernel_methods!(
380    ArrowRleBatch,
381    " Keys are outer-joined and shorter sides padded with [`default_fill`]."
382);
383
384impl PartialEq for ArrowRleBatch {
385    /// Same keys in the same order, and equal sequences for each.
386    fn eq(&self, other: &Self) -> bool {
387        self.keys == other.keys && self.iter().zip(other.iter()).all(|(a, b)| a == b)
388    }
389}