Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
460ddba
nM: STAR's accumulated mismatch count, not a recount
BenjaminDEMAILLE Oct 29, 2026
040b847
Write the forward-strand mate of a pair first
BenjaminDEMAILLE Oct 29, 2026
b00182f
Drop the mate-2 exon-subset dedup of finalized pairs
BenjaminDEMAILLE Oct 29, 2026
c94e6c3
Drop the read when a window full of anchors receives another align
BenjaminDEMAILLE Oct 29, 2026
d8322e5
STARsolo: STAR's EM for --soloMultiMappers, zeros written
BenjaminDEMAILLE Oct 29, 2026
824cc3f
STARsolo: keep gene-ambiguous reads with a 1MM_multi barcode
BenjaminDEMAILLE Oct 29, 2026
1fd99a3
STARsolo: --soloUMIfiltering for the SJ feature
BenjaminDEMAILLE Oct 29, 2026
0144a96
Docs: the DIVERGENCE.md 5 residuals are fixed
BenjaminDEMAILLE Oct 29, 2026
7e2212c
Splice-junction database: FxHashMap instead of SipHash
BenjaminDEMAILLE Oct 29, 2026
73f7afe
Stitcher: store a recorded transcript's window score and mapped length
BenjaminDEMAILLE Oct 29, 2026
457c34c
Junction scan: stop once no later position can win
BenjaminDEMAILLE Oct 29, 2026
45aa4c9
Stitcher: memoize flank extensions in large windows
BenjaminDEMAILLE Oct 29, 2026
c44c54b
Transcript dedup: compare CIGARs without building strings
BenjaminDEMAILLE Oct 29, 2026
a29228f
Stitcher: backtrack on one transcript instead of cloning at each branch
BenjaminDEMAILLE Oct 29, 2026
e7853ec
Junction scan: fetch the scan's bases in bulk
BenjaminDEMAILLE Oct 29, 2026
aeba641
Stitcher: keep the window memo tables between windows
BenjaminDEMAILLE Oct 30, 2026
bcddd31
Remove dead code; stdout writers are the file writers on another output
BenjaminDEMAILLE Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,55 @@ Sections commonly used: Features, Bug fixes, Other changes.

## [Unreleased]

### Bug fixes

- The residual differences in DIVERGENCE.md §5 are fixed. On the parity
suite's yeast (10k and 100k, SE and PE, chimeric, `--outSAMunmapped
Within`), human chr21 (SE, PE) and fusion runs, alignments, `SJ.out.tab`
and `Log.final.out` now match STAR 2.7.11b, mate order included; on the
mouse chr19 STARsolo runs every `Solo.out` file does.
- `nM` is STAR's mismatch count as accumulated while stitching, not a
recount of the final alignment. The two differ when a junction scan moves
a junction back over bases already counted as mismatches against the
intron: STAR counts them again and keeps both counts.
- The two mates of a pair are written in STAR's order: the one on the
forward strand first, so mate 2 leads when mate 1 is reverse. Every
paired alignment was mate 1 first before, about half of STAR's.
- A pair whose two alignments share their mate positions but place an
indel or junction differently inside one mate is no longer collapsed to
one alignment. The extra mate-2 dedup that did it is gone; STAR's window
dedup compares the whole pair.
- A read is dropped as "other" when a window full of anchors receives one
more align, whatever that align is (STAR's
`MARKER_TOO_MANY_ANCHORS_PER_WINDOW`). A non-anchor arriving there was
skipped instead, so the read could map or count as "too short".
- STARsolo `--soloMultiMappers EM` is STAR's EM: it starts from the unique
counts plus the uniform spread, zeroes values below 0.01 at the start of
each round, and stops once no gene moves by 0.01 (or after 101 rounds),
so a gene can keep a value below 0.01. Every gene of a cell's unique or
multi-gene counts is written to `UniqueAndMult-*.mtx`, a zero included.
- A gene-ambiguous read whose barcode has several 1MM whitelist matches is
kept for `--soloMultiMappers` and its barcode resolved at counting time,
as for a unique-gene read; one it cannot resolve counts as
`noTooManyWLmatches`. It was dropped before.
- `--soloUMIfiltering` applies to the STARsolo `SJ` feature as it does to
genes: a read that crosses two junctions is one UMI on two features, a
tie `MultiGeneUMI_CR` drops.

### Other changes

- Dead code removed: the per-seed pre-extension scoring block in the stitcher,
which ran for every window and whose result was discarded; the reverse-strand
branches of the stitcher and junction scan, unreachable since windows are
stitched in forward coordinates; and `BufferedBamRecords`,
`SamWriter::write_alignment`, `Seed::find_paired_seeds`,
`Seed::get_genome_positions`, `combined_transcript_for_projection`,
`parse_gtf`, `extract_junctions_from_exons`,
`AlignmentScorer::score_gap_with_strand`. **API removal.** The stdout writers
are now the file writers on another output (`SamWriter<W>`, `BamWriter<W>`,
`SortedBamWriter::create_stdout`), so `--outStd SAM` gets the file writer's
`[SAM-MISMATCH]` check. Output is unchanged.

- Third-party notices completed. `LICENSES/` gains the licences of code
ported from DegNorm (MIT) and David Elworthy's `sgt.h` via STAR (BSD
2-clause), and of libc++ (Apache 2.0 with LLVM Exceptions), whose random
Expand Down
7 changes: 1 addition & 6 deletions DIVERGENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,7 @@ rustar-aligner uses an in-tree splitmix64 (`src/rng.rs`) rather than the `rand`

These are **not** deliberate divergences. They are the differences `test/parity_suite.sh` finds, kept here so that each one is fixed or explained before v0.2.0 (README, *Comparison with STAR 2.7.11b*). Read names are from that suite's datasets.

- **`nM` and mate order on multi-mapping pairs.** Human chr21 `ERR188021.1648513` and `ERR188021.1662773`, plus 21 pairs of the fusion set (e.g. `fustut00000271e5`, `fustut00000282b2`). The loci, CIGARs, `AS` and MAPQ match STAR's. On some alignments `nM` is 1–2 lower than STAR's, and in 11 of the 21 fusion pairs the mates are written in the opposite order.
- **Same-span alignments collapsed.** Fusion set `fustut000009a03e`, `fustut000009a03f`, `fustut0000167ff6`. STAR keeps two alignments with the same mate positions but different indel or splice placement inside one mate; rustar-aligner keeps one, so `NH` is one lower.
- **A pair STAR leaves unmapped.** Human chr21 `ERR188021.1789477`: rustar-aligner maps it to 10 loci (`AS` 136, spliced), adding 2 lines to `SJ.out.tab`.
- **Unmapped-read category.** Some unmapped reads are counted as "too short" in `Log.final.out` (and `uT:1`), where STAR has "other" (`uT:0`): yeast 100k PE 1 (`ERR12389696.13360341`), human chr21 PE 17, fusion 73. For the yeast pair, STAR builds no alignment window, while rustar-aligner builds one, aligns 31 points and rejects it as too short.
- **STARsolo `GeneFull` multi-mappers.** With `--soloMultiMappers`, STAR counts one multi-gene read whose barcode needed 1MM_multi correction, and rustar-aligner drops it (`UniqueAndMult-*.mtx`, one `Features.stats` line).
- **STARsolo `SJ` with `--soloUMIdedup 1MM_CR`.** STAR collapses 4 more SJ UMIs than rustar-aligner on the mouse chr19 fixture (SJ `matrix.mtx`, `Features.stats`, `Summary.csv`).
There are none at present. The six once listed here (`nM` and mate order, same-span alignments, a pair STAR leaves unmapped, the unmapped-read category, STARsolo `GeneFull` multi-mappers and `SJ` UMI filtering) now match STAR on the suite's yeast, chr21, fusion and STARsolo runs.

---

Expand Down
21 changes: 8 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A Rust reimplementation of [STAR](https://github.com/alexdobin/STAR) (Spliced Tr

rustar-aligner aims to be a faithful port of STAR, matching the original behavior as closely as possible. It uses the same genome index format, accepts the same `--camelCase` command-line parameters, and produces compatible SAM/BAM output.

**Current status (v0.1, working toward v0.2.0):** single-end and paired-end alignment, splice junctions, two-pass mode, chimeric detection, `--quantMode GeneCounts` / `TranscriptomeSAM`, STARsolo (Gene / GeneFull / SJ / Velocyto, barcode correction, UMI dedup, multi-mapper resolution, cell calling), WASP filtering, mate-overlap merging, coverage tracks, adapter clipping, sparse suffix arrays and multi-threading. On the [parity suite](#comparison-with-star-2711b) the genome indices are byte-identical to STAR's, and alignments, `SJ.out.tab`, gene counts, chimeric junctions and STARsolo output match STAR 2.7.11b record for record, apart from the [residual differences](#residual-differences) listed below. Every deliberate difference is in [DIVERGENCE.md](DIVERGENCE.md); the release plan is in [ROADMAP.md](ROADMAP.md#release-plan).
**Current status (v0.1, working toward v0.2.0):** single-end and paired-end alignment, splice junctions, two-pass mode, chimeric detection, `--quantMode GeneCounts` / `TranscriptomeSAM`, STARsolo (Gene / GeneFull / SJ / Velocyto, barcode correction, UMI dedup, multi-mapper resolution, cell calling), WASP filtering, mate-overlap merging, coverage tracks, adapter clipping, sparse suffix arrays and multi-threading. On the [parity suite](#comparison-with-star-2711b) the genome indices are byte-identical to STAR's, and alignments, `SJ.out.tab`, gene counts, chimeric junctions and STARsolo output match STAR 2.7.11b record for record; [residual differences](#residual-differences) lists any that remain. Every deliberate difference is in [DIVERGENCE.md](DIVERGENCE.md); the release plan is in [ROADMAP.md](ROADMAP.md#release-plan).

## Quick Start

Expand Down Expand Up @@ -139,17 +139,17 @@ Alignment records are compared per read: with more than one thread STAR writes r
| Yeast 100k | PE, `--quantMode TranscriptomeSAM GeneCounts`, sorted BAM | 188,258 / 188,258 | 0 | identical |
| Yeast 100k | PE, `--twopassMode Basic` | 188,262 / 188,262 | 0 | identical |
| Yeast 100k | PE, `--outFilterType BySJout` | 187,646 / 187,646 | 0 | identical |
| Yeast 100k | PE, `--outSAMattributes All --outSAMstrandField intronMotif --outSAMunmapped Within` | 218,994 / 218,996 | 1 (unmapped pair, `uT`) | identical |
| Yeast 100k | PE, `--outSAMattributes All --outSAMstrandField intronMotif --outSAMunmapped Within` | 218,996 / 218,996 | 0 | identical |
| Yeast 100k | PE, `--genomeSAsparseD 2` index | 188,436 / 188,436 | 0 | identical |
| Yeast 100k | SE / PE, chimeric (`--chimSegmentMin 12`, `--chimMultimapNmax` 0 and 10) | all | 0 | identical |
| Human chr21 | SE (mate 1) | 541,935 / 541,935 | 0 | identical |
| Human chr21 | PE | 902,186 / 902,190 | 3 | 3,071 / 3,073 lines shared |
| Fusion | PE, chimeric (`--chimMultimapNmax` 0 and 10) | 80,592 / 80,656 | 24 | identical |
| Human chr21 | PE | 902,190 / 902,190 | 0 | identical |
| Fusion | PE, chimeric (`--chimMultimapNmax` 0 and 10) | 80,656 / 80,656 | 0 | identical |

- **Gene counts** (`ReadsPerGene.out.tab`): identical on yeast. On chr21 PE, 274 of 276 lines are identical; the other 2 come from the reads that differ.
- **Transcriptome BAM** (`--quantMode TranscriptomeSAM`): every record is identical on yeast (138,084) and chr21 (28,972) except for *which* alignment of a multi-mapping read carries the primary flag. STAR draws that from a per-thread random stream, so its choice depends on how reads are split across threads. rustar-aligner reproduces STAR's `--runThreadN 1` choice at any thread count, and against STAR run single-threaded the yeast transcriptome BAM is identical record for record. See [DIVERGENCE.md §1.1](DIVERGENCE.md#11-multimapper-tie-breaking--rng).
- **Chimeric junctions** (`Chimeric.out.junction`): the same lines as STAR in every configuration: yeast SE 194, PE 717 / 882 (`--chimMultimapNmax` 0 / 10); chr21 PE 301 / 3,643; fusion 31 / 675. Only the order differs, for the threading reason above.
- **`Log.final.out`**: identical on yeast SE and 10k PE. Elsewhere the mapped counts differ only by the reads in the table, and a few unmapped reads are counted as "too short" rather than "other" (yeast 100k PE 1, chr21 PE 17, fusion 73).
- **`Log.final.out`**: identical on yeast (10k and 100k, SE and PE, chimeric, `--outSAMunmapped Within`), chr21 SE and PE, and fusion.

### Single-cell (STARsolo)

Expand All @@ -158,20 +158,15 @@ Mouse chr19, 10x v2, `--soloType CB_UMI_Simple --soloFeatures Gene GeneFull SJ V
| Options | BAM records identical | `Solo.out` files identical |
|---------|-----------------------|----------------------------|
| defaults, sorted BAM with `CR UR CB UB GX GN sS sQ sM` | 19,955 / 19,955 | 36 / 36 |
| `--soloCellFilter EmptyDrops_CR --soloMultiMappers Uniform EM --soloUMIdedup 1MM_CR --soloCBmatchWLtype 1MM_multi_Nbase_pseudocounts --soloUMIfiltering MultiGeneUMI_CR` | (no BAM) | 34 / 40 |
| `--soloCellFilter EmptyDrops_CR --soloMultiMappers Uniform EM --soloUMIdedup 1MM_CR --soloCBmatchWLtype 1MM_multi_Nbase_pseudocounts --soloUMIfiltering MultiGeneUMI_CR` | (no BAM) | 40 / 40 |

In the default run every file matches: raw and filtered matrices, `barcodes.tsv` / `features.tsv`, `Summary.csv`, `Features.stats`, `Barcodes.stats` and `UMIperCellSorted.txt`. In the second run the EmptyDrops_CR cell calls match too, because rustar-aligner draws from the same random-number generator as STAR. The 6 files that differ are explained under [residual differences](#residual-differences).
In the default run every file matches: raw and filtered matrices, `barcodes.tsv` / `features.tsv`, `Summary.csv`, `Features.stats`, `Barcodes.stats` and `UMIperCellSorted.txt`. In the second run the EmptyDrops_CR cell calls match too, because rustar-aligner draws from the same random-number generator as STAR.

### Residual differences

These aren't deliberate. Each is tracked to be fixed or explained before v0.2.0; [DIVERGENCE.md §5](DIVERGENCE.md#5-known-residual-single-read-differences) has the read names.

- **`nM` and mate order on some multi-mapping pairs** (chr21 2 pairs, fusion 21 pairs). Same loci, CIGARs, `AS` and MAPQ. On some alignments `nM` is 1–2 lower than STAR's, and in about half of these pairs the two mates are written in the opposite order.
- **Same-span alignments collapsed** (fusion, 3 pairs). STAR reports two alignments with the same mate positions but different indel or splice placement inside a mate; rustar-aligner reports one, so `NH` is one lower.
- **A pair STAR leaves unmapped** (chr21, 1 pair). rustar-aligner maps it to 10 loci (`AS` 136, spliced). This adds 2 lines to `SJ.out.tab`.
- **Unmapped-read category** ("too short" versus "other", `uT`), for the counts listed above. The yeast pair: STAR finds no alignment window (`uT:0`); rustar-aligner finds a 31-point alignment and rejects it as too short (`uT:1`).
- **STARsolo `GeneFull` multi-mappers** (`--soloMultiMappers`). STAR counts one multi-gene read whose barcode needed 1MM_multi correction, and rustar-aligner drops it. This changes `UniqueAndMult-*.mtx` and one `Features.stats` line.
- **STARsolo `SJ` with `--soloUMIdedup 1MM_CR`**. STAR collapses 4 more SJ UMIs than rustar-aligner does (SJ `matrix.mtx`, `Features.stats`, `Summary.csv`).
None at present.

### Speed and memory

Expand Down
2 changes: 0 additions & 2 deletions examples/jrbench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ fn main() {
0,
del as i64,
&genome,
false,
N as u64,
75,
iters_per_scan,
);
Expand Down
Loading
Loading