Skip to main content

A Python interface to rammap, the Rust implementation of minimap2

Project description

🔗🐍🧬 rammappy Stars

PyO3 bindings and Python interface to rammap, the Rust implementation of minimap2.

Release PyPI ruff ty Code style: black

⚠️ Warning

This library is still a work-in-progress, and in an experimental stage, with API breaks very likely between minor versions.


🗺️ Overview

minimap2 is a widely used mapping tool for nucleotide sequences. rammap1 is a reimplementation of minimap2 in Rust, demonstrating perfect concordance while enabling performance optimizations for modern architectures. It maintains Rust's stronger memory safety constraints and provides a modular architecture.

rammappy is a Python module, implemented using the PyO3 framework, that provides bindings to rammap. It directly links to the rammap-core crate, which provides the following advantages:

  • zero-copy: Sequences are passed around as contiguous blocks of C-memory storing the byte sequences.
  • multithreaded: True parallel alignment inside a detached thread context that completely drops the Python GIL during batch processing.
  • lazy evaluation: Iterator-based result generation ensures massive mapping tasks don't blow up system memory.

🔧 Installing

rammappy can be installed directly from PyPI, which hosts some pre-built CPython wheels.

If you are building from source or working on development, we prefer using uv for environment and dependency management, along with just as a command runner:

just install

(This command wraps uv venv --allow-existing and uv pip install -e .)

💡 Examples

🧬 Parsing FASTA/FASTQ Files

rammappy includes a high-performance parsing module for FASTA and FASTQ files. It can automatically detect the file format and supports reading from disk, parsing directly from memory (bytes), or even streaming data in chunks!

Reading from a file

import rammappy

# FastxReader automatically detects FASTA or FASTQ and handles them efficiently
reader = rammappy.FastxReader("sequences.fa")
for record in reader:
    print(f"Name: {record.name}")
    print(f"Sequence: {record.sequence}")
    if record.quality:
        print(f"Quality: {record.quality}")

# Or use the convenience function to read all records at once
seqs = rammappy.read_fasta("sequences.fa")

Parsing from memory

# Parse bytes directly without touching the disk
data = b">seq1\nACGT\n>seq2\nGGGG\n"
seqs = rammappy.parse_fasta_bytes(data)

Streaming data For network streams or chunked processing, you can push chunks of bytes and process records as they become available:

streamer = rammappy.FastaStreamer(rna_to_dna=False)
streamer.push(b">seq1\nACGT\n>seq2\n")

# Process records that are complete
while (record := streamer.next_record()) is not None:
    print(f"Ready: {record[0]}")  # Prints seq1

# Push more data later
streamer.push(b"GGGG\n")
streamer.finalize()

while (record := streamer.next_record()) is not None:
    print(f"Ready: {record[0]}")  # Prints seq2

🧠 Building an Index In-Memory

A significant advantage of rammappy over mappy (the official minimap2 Python bindings) is the ability to build an Index directly from in-memory sequences. In mappy, reference sequences must generally be read from a FASTA file on disk. rammappy allows you to skip the disk I/O entirely:

import rammappy

# Define reference sequences
refs = [
    (b"chr1", b"ACGT" * 1000),
    (b"chr2", b"TGCA" * 1000)
]

# Build the index completely in-memory
index = rammappy.Index.build(refs, k=15, w=10)

# Optionally save it for later use
index.save("reference.mmi")

# You can also fetch sequences directly from the index!
print(index.seq("chr1", start=0, end=10))

🔨 Instantiating an Aligner

You can create an aligner by providing the built index and a preset configuration.

# Pass the in-memory index directly to the Aligner
aligner = rammappy.Aligner(index, preset=rammappy.Preset.Sr)

🔬 Batch Alignment

Querying multiple sequences can be done via map_batch, which drops the GIL and runs in parallel using Rust's Rayon ecosystem:

queries = [
    (b"query1", b"ACGT" * 20),
    (b"query2", b"CGTA" * 20),
]

batch_results = aligner.map_batch(queries)

# Lazy-evaluate the iterator to pull hits
for i, mappings in enumerate(batch_results):
    first_hit = next(iter(mappings), None)
    if first_hit:
        print(f"Query {i+1} mapped to {first_hit.target_name.decode()} at {first_hit.target_start}")

📊 Exploring Sketchers

Direct access to seeding algorithms is available for customized genomic sketching:

sketcher = rammappy.MinimizerSketcher(kmer_len=15, window_len=10)
seed = sketcher.sketch(b"ACTG" * 50)[0]
print(f"Minimizer at position {seed.x} with y-value {seed.y}")

🏗️ Architecture & Implementation Philosophy

The overriding goal for this project was to establish extremely performant, zero-copy FFI bindings linking the Rust core API to Python. A focus was applied to maintain "bare metal Rust" speeds while providing a clean and "lazy" Python API.

graph TD
    A[Python Application] -->|Bytes & Queries| B[rammappy PyO3 FFI]
    B -->|GIL Dropped| C[Rayon Thread Pool]
    C -->|Zero-Copy Raw Pointers| D[rammap-core]
    D -->|Alignment Results| C
    C -->|Lazy Iterators| B
    B -->|Python Mapping Objects| A

Zero-Copy Evaluation Using bytes

Python strings (str) perform computationally expensive UTF-8 allocations and validation mechanisms. rammappy universally prefers Python byte-strings (bytes in Python, mapped to &[u8] in Rust). Data entering the alignment algorithm uses Bound<'py, PyBytes>, mapping directly to contiguous blocks of C-memory storing the sequences. Retrieving genomic strings mapping fields (e.g., CIGAR/MD/CS strings) exposes byte payloads directly without additional UTF-8 reallocations during FFI crossings.

In-Memory Indexing vs Disk I/O

Traditional bindings like mappy force users to write target sequences to a FASTA file before an index can be built and queried. rammappy decouples the Index from the Aligner, allowing indexes to be built dynamically from raw memory bytes inside Python, completely eliminating disk I/O bottlenecks for dynamic or programmatic reference generation.

Parallel Scaling and the Python GIL

Scaling genomic query alignments in parallel on multi-core systems mandates threading. However, the presence of the Python Global Interpreter Lock (GIL) poses problems, as normal PyO3 structures retain a lock on the main Python thread.

The Solution: We collect batches of targets mapped to pointers and lengths. We wrap these representations in a custom struct RawQuery { name_ptr, seq_ptr, ... }. We implement unsafe impl Send for RawQuery and unsafe impl Sync for RawQuery, allowing pointer transmission across thread boundaries. The GIL is then released via py.detach(|| { ... }), and rayon handles iterating and distributing alignments across all CPU cores. unsafe { std::slice::from_raw_parts } safely rebuilds the byte vectors in the isolated thread spaces, as the parent function's stack guarantees the memory allocation outlives the closure.

Lazy Materialization

Rather than computing alignment lists as heavy Vec<Mapping> aggregates and immediately converting every hit to a Python-native object (costing heavy FFI time), rammappy returns a MappingIterator. A MappingIterator acts as an opaque handle holding the vector of internal RustMapping entries. Only when a user invokes next(iterator) is the memory read and a single Python Mapping object initialized and surfaced over the FFI boundary.


👏 Acknowledgments

We would like to thank the original authors of rammap, Jeremy R. Wang and Heng Li, for their high-performance reimplementation of minimap2 in Rust.

📚 References

  1. Jeremy R. Wang and Heng Li. Memory-safe high-performance sequence mapping with rammap (2026). bioRxiv. 10.64898/2026.05.26.726289.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rammappy-0.1.1.tar.gz (90.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rammappy-0.1.1-cp310-abi3-win_amd64.whl (676.7 kB view details)

Uploaded CPython 3.10+Windows x86-64

rammappy-0.1.1-cp310-abi3-manylinux_2_34_x86_64.whl (909.3 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.34+ x86-64

rammappy-0.1.1-cp310-abi3-macosx_11_0_arm64.whl (750.3 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file rammappy-0.1.1.tar.gz.

File metadata

  • Download URL: rammappy-0.1.1.tar.gz
  • Upload date:
  • Size: 90.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rammappy-0.1.1.tar.gz
Algorithm Hash digest
SHA256 30be710210f7ee5a5f544a821a2607258eb06bdb085a63746c56c5c3f6ebdc99
MD5 9bdc4838857e378c77e55809de5bd10d
BLAKE2b-256 1955e884557b2a986594a0d1d976e0f1ee58630058e501a5d50e730b366cf421

See more details on using hashes here.

Provenance

The following attestation bundles were made for rammappy-0.1.1.tar.gz:

Publisher: publish.yml on tomdstanton/rammappy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rammappy-0.1.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: rammappy-0.1.1-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 676.7 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rammappy-0.1.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 560ddca317dcd5c6c91a785764acced1283c83d2557ca130e37d3dbb6a3401ff
MD5 64a3433d4992b6ec3170a7419afc4c1a
BLAKE2b-256 63a94ea393bd5fa6b27cecca3c46b3b3dbbbeea0b9e3d3cb6e205b60df32aaa3

See more details on using hashes here.

Provenance

The following attestation bundles were made for rammappy-0.1.1-cp310-abi3-win_amd64.whl:

Publisher: publish.yml on tomdstanton/rammappy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rammappy-0.1.1-cp310-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rammappy-0.1.1-cp310-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 a68451d93f0938d6c58af52d21e3246a9073f9792705902e6819080de40617b3
MD5 28735d398de9ad89e57d206233a7d678
BLAKE2b-256 a21f46d910b974fa8cbdc58edfe2ed15be45a90e9a134f760d7644310ea64a73

See more details on using hashes here.

Provenance

The following attestation bundles were made for rammappy-0.1.1-cp310-abi3-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on tomdstanton/rammappy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rammappy-0.1.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rammappy-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9f07c15d2ca1025a0bc0a87489d6629f978734d6ab89550fb0dfdf6cb546c783
MD5 d2715a6c2ad8e0575cdb041bacd9bb98
BLAKE2b-256 3bb97354c5e7749df60d25befc5f55d0a2f5dabbdc6c97d0e0e8fcdb40b76c50

See more details on using hashes here.

Provenance

The following attestation bundles were made for rammappy-0.1.1-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on tomdstanton/rammappy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page