Porting GenomicRanges and IRanges functions needed for genomicdist to gtars-core #229
Replies: 3 comments
|
I would add all of this function to |
|
Also check |
Porting GenomicRanges Interval Operations to Rust: Proof-of-Concept BriefExecutive SummaryWe implemented 5 core GenomicRanges/IRanges interval operations ( Benchmark ResultsAll times in microseconds (median). Rust compiled in release mode. R 4.5 with GenomicRanges/BiocGenerics. macOS ARM64 (Apple Silicon). Synthetic Data (random regions on chr1-22, lognormal lengths 100-10,000 bp)
Scaling Curves
Speedup by Scale
Real ENCODE DataBenchmarked on 4 ENCODE BED files spanning typical genomic analysis sizes. All times in microseconds (median).
Real data confirms the synthetic trends: Scaling AnalysisRust advantages are largest at small scales (1K-10K), where R's S4 dispatch and GRanges object overhead dominates. At 1M scale, Rust still wins on R wins on
Accuracy ValidationSynthetic DataAll 5 functions produce identical output on shared synthetic inputs (1K and 10K scales tested). Real ENCODE DataValidated on all 4 ENCODE BED files using shared inputs (Rust exports BED files, R reads and processes the same files, outputs compared line-by-line after sorting).
17/20 pass. All 3 failures are explained by intentional behavioral differences (see below). Intentional Behavioral DifferencesAll mismatches trace to documented design decisions, not bugs:
Arguments for Full Port
Scope Estimate
RecommendationProceed to the partition system port. The 5 functions implemented here are direct prerequisites for Artifacts
|




Uh oh!
There was an error while loading. Please reload this page.
Porting 5 GenomicRanges interval operations to gtars-core
These 5 functions from R's GenomicRanges/IRanges are prerequisites for the partition system in genomicdist. None exist in gtars yet.
Where to put the code
File:
gtars-core/src/models/region_set.rsAdd new methods inside the existing
impl RegionSet { ... }block (starts at line 283). This is wheresort(),region_widths(),calc_mid_points(), etc. already live. The new interval operations follow the same pattern — methods on RegionSet that return new RegionSets.Tests: Add to the existing
#[cfg(test)] mod tests { ... }block at the bottom of the same file (starts at line 638). Follow the existing pattern usingrstestand theget_test_path()helper.Test data:
tests/data/regionset/— the existingdummy.bedfile has overlapping regions on chr1 that work well for testing reduce and setdiff:You may want to create a second test file (e.g.
dummy_b.bed) to use as the "subtract" set for setdiff testing.Run tests with:
cargo test -p gtars-coreCoordinate system note
R/Bioconductor uses 1-based, closed intervals. BED format and gtars use 0-based, half-open intervals. The R descriptions below document R's actual behavior. The Rust signatures and algorithms are adapted for 0-based half-open coordinates. Keep this distinction in mind throughout.
Function 1:
trim(trivial warmup)What it does in R
GenomicRanges::trim(x)— clamps out-of-bound ranges to valid chromosome coordinates. Only applies to ranges on non-circular sequences whoseseqlengthsare known (not NA). Ranges extending below position 1 are clamped to 1; ranges extending pastseqlengthsare clamped to the chromosome length.Important R behavior:
trim()never drops ranges. It always returns an object of the same length as the input. Ranges that collapse to zero width after clamping become empty ranges (width 0), not removed.R usage
Algorithm (adapted for 0-based half-open in Rust)
Rust signature
Test idea
Function 2:
promoters(trivial warmup)What it does in R
GenomicRanges::promoters(x, upstream=2000, downstream=200)— creates promoter regions relative to each gene's transcription start site (TSS).This function is strand-aware. The TSS depends on strand:
start(x)(leftmost coordinate)end(x)(rightmost coordinate, since transcription goes right-to-left)R formulas (1-based, closed)
For + and * strand:
For - strand:
R usage
Note: this can produce out-of-bound coordinates (negative starts), which is why
trim()is always called after.Algorithm (adapted for 0-based half-open, strand-unaware)
BED regions in gtars typically lack strand information. For the Rust port, we treat all regions as + strand (TSS = start):
Rust signature
Test idea
Note: since
startisu32, you'll need to handle the underflow case (upstream > start). Usestart.saturating_sub(upstream)or check before subtracting.Function 3:
reduce(first real exercise)What it does in R
GenomicRanges::reduce(x)— merges overlapping and adjacent intervals into a minimal non-overlapping set. By default, operates independently per (chromosome, strand) group.Key parameters:
ignore.strand=FALSE(default): reduces per (seqname, strand). Set toTRUEto treat all strands as*.min.gapwidth=1L(default): adjacent ranges (gap of 0) are merged. Increase to merge ranges separated by larger gaps.with.revmap=FALSE(default): whenTRUE, adds a metadata column mapping output ranges back to input ranges.drop.empty.ranges=FALSE(default): whether to exclude empty ranges from output.Names and metadata columns from the input are dropped in the output.
R usage
Algorithm
R example with dummy.bed
Rust signature
Key Rust concepts you'll use
self.regions.clone()thensort_by()— or use the existingsort()methodVec<Region>withpush()std::cmp::max()for merging endsTest idea
Also test with non-overlapping regions to make sure they stay separate.
Function 4:
setdiff(the real workout)What it does in R
GenomicRanges::setdiff(x, y, ignore.strand=FALSE)— subtracts interval set Y from interval set X. Returns the portions of X that don't overlap with any region in Y. Can split one region into multiple pieces.Important: This is a set operation. Both inputs are implicitly reduced before subtraction, and the output is also reduced. If X has overlapping ranges, they are merged first. The operation is performed per (chromosome, strand) group by default.
R usage
Algorithm
Worked example
Edge cases to handle
Rust signature
Key Rust concepts you'll use
self.reduce()andother.reduce()firstHashMap<&str, Vec<&Region>>to group B regions by chromosomepush()Test ideas
You'll probably want to create test RegionSets inline from
Vec<Region>usingRegionSet::from(vec![...])rather than loading from files, since you need specific geometries.Function 5:
pintersect(easy after the others)What it does in R
IRanges::pintersect(x, y)— given two GRanges of the same length (already paired up by overlap finding), returns the pairwise intersection of each pair. This is NOT a set operation — it operates on pre-matched pairs.Important R behavior:
pintersectnever drops pairs. It always returns an object of the same length as the input. When two ranges don't overlap, the result is an "ambiguous empty range" controlled by theresolve.emptyparameter:"none"(default): throws an error if any pair has no overlap"max.start": assigns the maximum start value to empty intersections"start.x": assigns the start of x to empty intersectionsR usage
Algorithm (core math)
Rust signature
Or as a free function:
Test idea
Summary: implementation order
trimpromotersreducesetdiffreducepintersectDo manually:
reduceandsetdiff(the two that teach real Rust patterns).Let AI handle:
trim,promoters,pintersect(trivial once you understand the codebase).All go in
region_set.rsas methods onimpl RegionSet, all tested withcargo test -p gtars-core.All reactions