runlength
Generic run-length encoded sequences in Rust, with plan-based arithmetic. It is
the successor to pyrle, with a new API and
any value type, not just f64.
| Crate | Contents |
|---|---|
runlength |
The core: generic Rle<V>, plans, merging, batches, coverage. Depends only on rayon. |
runlength-arrow |
The same over Apache Arrow arrays, so values can be any Arrow type, and batch keys are Arrow tables. |
runlength-py |
The runlength Python package (pyo3). Works with polars, pandas, pyarrow and numpy; none of them is required. |
Python
=
=
= # an RleBatch
# Rle(length=3, runs=1, dtype=Int64) ...
# Chromosome, Strand, start, end, value
# Chromosome, Strand, mean
= # add the strands together
, = # back to intervals
=
# mean coverage per interval, no copies
= # values of any type
=
# any function, any library
# or just the plan: (ends, idx_a, idx_b)
Inputs can be polars or pandas Series and DataFrames, pyarrow or numpy arrays,
or lists. Outputs (rle.values, rle.runs, batch.keys, ...) speak the Arrow
PyCapsule interface, so pl.Series(x), pa.array(x) and np.asarray(x) read
them without copying. ==, < etc. are element-wise and return boolean Rles;
a.equals(b) compares whole sequences.
Build it locally with cd crates/runlength-py && maturin develop --release.
use *;
let a = from_runs?;
let b = from_runs?;
// The plan only looks at run boundaries...
let p = a.plan_with;
assert_eq!;
assert_eq!;
// ...so any operation works on any value types.
let labels = a.zip_with?;
// Operators pad the shorter side with zero and merge the result.
assert_eq!;
What's in it
| Area | API |
|---|---|
| Sequences | Rle<V> (owned), RleRef<V> (borrowed), shared methods on RleLike |
| Combining | plan, zip_with, zip_with_raw, + - * / & | ^, unary - and !, elem_eq/ne/lt/le/gt/ge |
| Merging | merge, merge_by(pred), merge_mask(mask, Keep), merge_plan(mask), is_merged |
| Slicing | get, slice, getitems (many slices → batch), slice_view and slice_reduce (reduce many slices without copying) |
| Rearranging | shift, reversed, concat, ranges_where |
| Many inputs | plan_many, zip_many |
| Reductions | sum, mean, variance(ddof), std(ddof), min, max (all weighted by run length) |
| Batches | RleBatch<K, V>: many sequences under any hashable keys, joins with How::{Outer, Inner, Left, Right}, collapse, concat, shift, reversed, slice_reduce, parallel with rayon |
| Coverage | coverage_sum, coverage_count, coverage_max, coverage_min (with _by_key variants), and overlap_plan / overlap_plan_by_key for any other aggregation |
Conventions
- Positions are
i64; every sequence starts at 0; intervals are[start, end). - Neighbouring runs merge when values are identical:
NaNmerges withNaN,-0.0does not merge with0.0. - Operations merge by default;
_rawvariants and plans do not. - Combining sequences of different lengths pads the shorter one with a fill
value. Operators use zero. With
zip_with, passingNonemakes unequal lengths an error.
See docs/design.md for the reasoning behind these choices.
Development
PROPTEST_CASES=5000 && &&
# Regenerate the pyrle cross-check cases (needs a Python that imports pyrle):
PYTHONPATH=/code/pyrle
# Check the reference port of pyranges' to_rle / to_bigwig against
# pyranges1 + pyrle (needs both installed, plus pyBigWig):
PYTHONPATH=/code/pyrle
Releasing
Versions of all three crates move together. Bump version in each
crates/*/Cargo.toml (and the runlength version that runlength-arrow
requires), commit, then push a tag:
&& &&
.github/workflows/release.yml checks that the tag matches the crate
versions, builds wheels for Linux (x86_64, aarch64), macOS (universal2) and
Windows, tests them, and publishes. Run it by hand from the Actions tab to
build and test the wheels without publishing.
One-time setup: add pyranges/runlength with workflow release.yml as a
trusted publisher on PyPI, and save a crates.io token as the
CARGO_REGISTRY_TOKEN repository secret.