polaranges 0.3.2

Rust-first genomic range operations on top of Polars DataFrames
Documentation
# polaranges

Draft for a Rust-first genomic range library built on top of Polars and
`ruranges-core`.

The first goal is a `RangeFrame` wrapper that:

1. owns a `polars::DataFrame`,
2. validates interval semantics (`Start`, `End`, non-null, `Start < End`),
3. factorizes `match_by` columns into dense `u32` group ids,
4. calls the overlap kernels in `ruranges-core`, and
5. uses the returned row indices to gather rows back out of Polars.

This keeps the heavy overlap logic in Rust while leaning on Polars for table
storage and projection.

## Why row indices first?

Polars does not have a pandas-style index, and that is fine here. The natural
primitive from `ruranges-core` is a pair of row-id vectors:

- `left[i]` is a row in the query `RangeFrame`
- `right[i]` is the matching row in the target `RangeFrame`

That means the library can expose both:

- `overlap_pairs(...) -> OverlapPairs`
- `overlap(...) -> RangeFrame`

where `overlap()` is only sugar for `self.take_rows(&pairs.left)`.

This is the same shape as `pyrunges`: the high-level operation is a thin layer
over a low-level kernel that returns row matches.

## Current draft scope

The draft in `src/` keeps v1 intentionally narrow:

- generic `RangeFrame` with configurable start/end column names
- `overlap_pairs()`
- `overlap()`
- row gathering with `take_rows()`
- pure Rust `match_by` factorization for strings, booleans, and integer columns

The draft currently normalizes coordinates to `i64` before calling
`ruranges-core`. That keeps the first version simple and fully Rust-native.
A later optimization can add zero-copy dispatch for `i32` and `i64`.