Skip to content

fix(group): label paired strands by read orientation in the parallel assigner - #1013

Merged
nh13 merged 1 commit into
mainfrom
nh/fix-parallel-paired-orientation
Oct 3, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/fix-parallel-paired-orientation

Conversation

@nh13

@nh13 nh13 commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

With --strategy paired, group prefixes each half of a read pair's UMI by which read it came from (aa: for the genomically earlier read, bb: for the later one) before assigning molecules. The canonical spelling is then always the one whose R1 is the earlier read, so /A is that strand and /B the other strand of the same molecule. This matches fgbio's GroupReadsByUmi.

The parallel paired assigner, which group uses under --allow-unmapped or --parallel-group-min-templates (with --threads > 1), received the raw UMI without those prefixes. That caused two problems:

  • Wrong strand labels. /A was whichever strand's RX happened to sort first, so about half of the molecules got the opposite strand labels from the sequential path and from fgbio.
  • Strands merged into one family. When a molecule's two UMI halves are identical (e.g. ACGT-ACGT, about 1 in 32 molecules with a fixed 32-UMI set), both strands have the same raw RX. They were put in the same family, so one single-strand consensus mixed reads from both strands.

Changes

  • group builds the same orientation-prefixed key for both paired assigners. ParallelPairedAssigner now exposes lower_read_umi_prefix() / higher_read_umi_prefix(), built exactly like PairedUmiAssigner's.
  • ParallelPairedAssigner accepts prefixed keys:
    • It 2-bit encodes only the prefix-stripped bases. These encodings are used for validity, counting and the uniform-length guard.
    • The prefixes always sort the lower-read spelling first. When they rule out every reverse-orientation match (equal-length prefixes differing in more than edits positions, which group's prefixes always do), it keeps the BitEnc fast path with forward edges only.
    • Strands are decided on the full prefixed keys, as in the sequential assigner.
    • Any other prefixed pool, or one with asymmetric halves, uses the existing full-string path, which reproduces the sequential relation exactly.
    • Unprefixed keys behave as before.
  • .coderabbit.yaml: the review instruction that described the old key contract now describes the new one.

Behavior changes

  • On the parallel path, paired strands are now labeled by read orientation, and molecules with identical UMI halves keep their strands apart.
  • Under --allow-unmapped, the two strands of an unmapped template pair no longer group into one molecule on the parallel path. Unmapped mates have no genomic order, so both strands get the same prefix order. This is what the sequential path already did (fgbio drops unmapped templates before grouping). Before this change the parallel path grouped them (as 0/A, 0/B) while the sequential path kept them as two molecules.

The default path (sequential assigner) is unchanged.

Tests

  • group end-to-end: test_paired_strand_follows_read_orientation (sequential and parallel) covers a molecule whose top-strand UMI sorts after its reverse and one with identical halves. The parallel case failed before the fix.
  • group end-to-end: test_allow_unmapped_paired_parallel_matches_sequential checks that --allow-unmapped gives the same molecules and strand labels on both paths. It failed before the fix.
  • Assigner level, comparing against the sequential assigner including the absolute /A//B labels: rstest cases (identical halves, halves sorting descending, an adjacency chain, invalid N UMIs, asymmetric halves, mixed case), plus two proptests over random prefixed pools (symmetric and asymmetric halves, edits 1–2, 1/4/16 threads).
  • Full-string fallback: a prefixed pool whose prefixes are too short to rule out reverse matches, and a pool mixing prefixed and plain keys, both match the sequential assigner. The first case fails if the gate that routes such pools to the full-string path is removed.
  • cargo ci-test, ci-lint, ci-fmt, ci-tag-literals and ci-doc pass.

group output changes for paired-UMI family assignments and /A//B strand labels; sequential-parity, orientation, and unmapped-pair tests pin the intended results. Unsafe: none added, so no CLAUDE.md allowlist update is needed. Memory bounds, queue capacity, and thread/backpressure policy: none changed.

The fix gives sequential and parallel paired assigners the same orientation-prefixed keys. The parallel path strips prefixes only for encoding and preserves full keys for comparisons and strand decisions. This prevents unmapped paired strands from merging when the sequential path keeps them separate.

…assigner

With --strategy paired, group gives each read pair's UMI an orientation
prefix (aa: for the half read by the genomically earlier read, bb: for the
later one) before UMI assignment. The canonical spelling is then always the
one whose R1 is the earlier read, so /A is the strand whose R1 maps first
and /B the other strand of the same molecule, as in fgbio.

The parallel paired assigner, which group uses under --allow-unmapped or
--parallel-group-min-templates, was handed the raw UMI without these
prefixes. Two things went wrong:

- /A followed the UMI spelling: it was whichever strand's RX sorted first,
  so about half of the molecules had their strands labelled the other way
  round from the sequential assigner and from fgbio.
- When a molecule's two UMI halves are identical (ACGT-ACGT), both strands
  have the same raw RX, so they were assigned the same family. That puts
  reads of both strands into one single-strand consensus.

group now sends both paired assigners the same prefixed keys, and the
parallel assigner takes the prefixes into account: it 2-bit encodes the
prefix-stripped bases, and when the prefixes rule out any reverse-orientation
match (they always do for group's prefixes, which are one longer than the
edit threshold) it uses forward edges only. Strands are decided on the full
prefixed keys, as in the sequential assigner. Any other prefixed pool is
grouped on the full strings, which matches the sequential relation exactly.
Unprefixed input behaves as before.

Behaviour change: on the parallel path, the two strands of an unmapped
template pair no longer group together. Unmapped mates have no genomic
order, so both strands get the same prefix order and their keys are not
reverses of each other. This matches the sequential assigner.
@nh13
nh13 deployed to github-actions October 3, 2026 19:12 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: fulcrumgenomics/fgumi/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: ab5aaadf-9103-475e-ae36-f955222fe4a3
📥 Commits

Reviewing files that changed from the base of the PR and between 33126e6 and 57cee36.

📒 Files selected for processing (3)
  • .coderabbit.yaml
  • src/lib/commands/group.rs
  • src/lib/umi/parallel_assigner.rs

Included review availability: This review used your included allowance. 1 included review remains after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


Walkthrough

Paired UMI keys now use orientation prefixes in both sequential and parallel assignment. The parallel assigner strips prefixes for encoding and uses full keys and prefixes when selecting comparison paths and assigning strands. Tests compare grouping and strand labels across both assigners.

Changes

Paired UMI assignment

Layer / File(s) Summary
Generate and expose paired-key prefixes
src/lib/commands/group.rs, src/lib/umi/parallel_assigner.rs, .coderabbit.yaml
Grouping adds orientation prefixes to paired UMIs for parallel assignment. The parallel assigner stores and exposes the prefixes for the genomically earlier and later reads.
Select edge and strand comparison paths
src/lib/umi/parallel_assigner.rs
The parallel assigner strips prefixes for encoding. It uses full-string comparisons when halves are asymmetric or prefixes permit reverse matches, and uses encoded paths in other cases. Strand selection uses the representation associated with the selected path.
Validate sequential and parallel parity
src/lib/commands/group.rs, src/lib/umi/parallel_assigner.rs
Tests compare molecule groups and strand labels for prefixed, unmapped, asymmetric, and randomized inputs.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Group as group.rs
  participant Assigner as ParallelPairedAssigner
  participant Encoder as BitEnc
  Group->>Assigner: Orientation-prefixed paired UMI keys
  Assigner->>Encoder: Prefix-stripped UMI bases
  Encoder-->>Assigner: Encoded UMI bases
  Assigner->>Assigner: Select comparison path and assign strands
Loading

Suggested labels: fgumi group

Merge Risk: ⚪ Minimal · up to 57cee

No actionable issue remains identified; the paired-UMI orientation change is mergeable after normal checks.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required conventional-commit format. It uses the allowed fix type, the affected group scope, and a lowercase imperative description without a period.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@nh13

nh13 commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai pause

@nh13

nh13 commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov

codecov Bot commented Oct 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.35484% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.42%. Comparing base (33126e6) to head (57cee36).

Files with missing lines Patch % Lines
src/lib/umi/parallel_assigner.rs 98.78% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1013      +/-   ##
==========================================
- Coverage   96.43%   96.42%   -0.01%     
==========================================
  Files         299      299              
  Lines      151142   151274     +132     
==========================================
+ Hits       145748   145870     +122     
- Misses       5394     5404      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

@nh13
nh13 added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 83162ba Oct 3, 2026
21 checks passed
@nh13
nh13 deleted the nh/fix-parallel-paired-orientation branch October 3, 2026 19:31
@nh13 nh13 mentioned this pull request Oct 2, 2026

This branch was successfully deployed

1 active deployment
github-actions — 57cee366 Deployed Oct 3, 2026 by nh13 via coverage #4788
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant