Repository navigation
fix(umi): make sequential and parallel assigners agree on UMI case - #455
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughMixed-case UMI inputs are now folded to uppercase before counting, matching, and result mapping in the sequential assigners, with adjacency tie-breaking updated to use case-folded ordering and parity tests added for sequential versus parallel behavior. ChangesCase-insensitive UMI assignment
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #455 +/- ##
==========================================
+ Coverage 91.05% 91.11% +0.06%
==========================================
Files 78 78
Lines 51324 51376 +52
==========================================
+ Hits 46731 46813 +82
+ Misses 4593 4563 -30 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/fgumi-umi/src/assigner.rs`:
- Around line 1614-1619: The paired helper methods `is_same_umi()` and
`canonicalize()` do not perform case-folding on their inputs while `assign()`
does through `upper_umis`, creating inconsistency in the case-insensitive
contract. Update both `is_same_umi()` (around line 1832-1836) and
`canonicalize()` (around line 1901-1904) to uppercase their input UMI parameters
before processing, matching the case-folding behavior already implemented in
`assign()`. This ensures mixed-case paired callers get consistent sequential
behavior that agrees with the parallel paired canonicalizer.
In `@src/lib/umi/parallel_assigner.rs`:
- Around line 1236-1311: The test functions
test_sequential_and_parallel_adjacency_agree_on_mixed_case,
test_sequential_and_parallel_edit_agree_on_mixed_case, and
test_sequential_and_parallel_paired_agree_on_mixed_case verify parity between
sequential and parallel implementations, but they do not verify compliance with
the fgbio baseline contract. Add programmatically generated expected outputs for
each test that validate the grouping/assignment results against the known fgbio
baseline behavior, or document and generate fixtures for any intentional
divergence from fgbio. This is especially important for
test_sequential_and_parallel_paired_agree_on_mixed_case since PairedUmiAssigner
is reachable via the CLI. The verification should ensure that the
output-changing UMI assignment behavior for mixed-case handling matches the
expected fgbio behavior.
- Around line 1303-1310: The current test at the assertion block starting with
same_partition() does not adequately verify that the reverse read keeps the same
base molecule ID as the forward reads. The current assert_ne check only verifies
that sequential[2] has a different partition from sequential[0], but this could
pass even if index 2 were assigned to a completely unrelated molecule. Add
explicit base-ID equality assertions after the same_partition() call to verify
that sequential[0], sequential[1], and sequential[2] all share the same base
molecule ID (just with different strand assignments), and add the same base-ID
equality checks for the parallel result to ensure both sequential and parallel
results maintain strand-assignment behavior correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 421a8d6e-e4a4-4da1-8468-0adbbd26c432
📒 Files selected for processing (2)
crates/fgumi-umi/src/assigner.rssrc/lib/umi/parallel_assigner.rs
1814ac2 to
b451fcf
Compare
b451fcf to
bceabf3
Compare
fgbio baseline validation for the case-folding changeFollowing up on the request for fgbio-baseline coverage: I drove fgbio's actual What fgbio does
ResultWith CLI-style uppercasing (what fgbio actually feeds its assigners), fgbio and fgumi produce identical groupings on mixed-case input:
So this PR makes fgumi converge with fgbio's user-facing output on mixed-case input — it does not diverge. The only difference is where the case fold happens: fgbio folds in the CLI before calling the assigner, while fgumi folds inside the assigner itself, which keeps the library API from being misused case-sensitively. On uppercase input (all real data) the fold is a no-op, so byte-for-byte parity with fgbio is unchanged there. The parity tests now pin these fgbio-derived groupings (not just sequential-vs-parallel agreement), and the |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/fgumi-umi/src/assigner.rs`:
- Around line 2446-2466: The new PairedUmiAssigner case-insensitivity tests
cover local behavior, but the output-changing UMI assignment semantics also need
generated fgbio-baseline identity coverage or an explicit documented divergence.
Add or reference a programmatic baseline test that asserts identity for the
affected edit, adjacency, and paired mixed-case inputs, using the existing
PairedUmiAssigner helpers and canonicalization flow so the expected behavior is
pinned against fgbio. If the new behavior intentionally differs from fgbio,
document that divergence in the same baseline test area instead of leaving only
local assertions.
In `@src/lib/umi/parallel_assigner.rs`:
- Around line 1339-1352: The paired fgbio-baseline test in parallel_assigner
only verifies same/opposite strand behavior, so a global A/B flip could still
pass; tighten the assertions in the sequential/parallel comparison block by
explicitly checking the expected PairedA/PairedB strand variants for indices 0,
1, and 2. Use the existing sequential/parallel test values in parallel_assigner
to anchor the check, and keep the base_id_string and opposite-strand assertions
alongside the new absolute orientation assertions so parity coverage remains
intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a8293fb1-f8c5-4b9a-b064-90a8c008de11
📒 Files selected for processing (2)
crates/fgumi-umi/src/assigner.rssrc/lib/umi/parallel_assigner.rs
|
@coderabbitai review |
✅ Action performedReview finished.
|
The adjacency, edit, and paired UMI assigners each have a sequential and a parallel implementation that are documented to produce identical groupings. The parallel implementations fold UMI case (count/match/tie-break on the uppercased UMI), but the sequential implementations did not, so on mixed-case UMIs the two could diverge: - Adjacency: the equal-count tie-break compared the raw first-seen string while the parallel assigner compared the uppercased string. Reachable only via direct library calls; the CLI pre-uppercases non-paired UMIs. - Edit: sequential matching was case-sensitive. Same CLI masking as adjacency. - Paired: sequential counting, matching, and strand assignment were all case-sensitive. NOT masked by the CLI, since the sequential paired path keeps raw case, so `group`/`dedup --threads 1` vs `--threads N` could group mixed-case paired UMIs (and assign strands) differently. Fold case in all three sequential assigners so they match their parallel counterparts. This is a no-op for the uppercase UMIs the CLI emits and does not change fgbio parity on standard (uppercase) data. Add sequential-vs- parallel parity tests on mixed-case input for all three strategies, plus a sequential-only case-folded tie-break test for the adjacency assigner.
bceabf3 to
5215dc8
Compare
Summary
Each UMI assignment strategy has a sequential implementation (
crates/fgumi-umi/src/assigner.rs) and a parallel one (src/lib/umi/parallel_assigner.rs), and both are documented to "produce identical results". The parallel implementations fold UMI case (they count, match, and tie-break on the uppercased UMI), but the sequential implementations did not. On mixed-case UMIs the two could therefore diverge:group/deduppre-uppercase non-paired UMIsgroup.rsbuildsprefix:part0-prefix:part1from the raw segments)The paired case is the only one reachable through the CLI today:
fgumi group/dedupselects the parallel paired assigner when--threads > 1and the sequential one otherwise, so on mixed-case paired/duplex UMIs the two thread settings could produce different groupings and different strand assignments. The adjacency and edit divergences are library-only, because the CLI uppercases non-paired UMIs beforeassign().Behavior comparison: fgbio main vs fgumi main vs this PR
How each tool/implementation handles UMI case, per strategy. "case-insensitive" =
acgtandACGTare treated as the same UMI; "case-sensitive" = they are treated as different.--threads 1)--threads N)Verified against fgbio
main(GroupReadsByUmi.scala): itsIdentityUmiAssigner.assignuppercases, but the edit/adjacency/paired assigners key on the raw (case-sensitive) UMI and the tool feeds them the rawRXtag — so fgbio is itself internally inconsistent on case across strategies.Takeaways:
BitEncby design.Fix
Fold case in all three sequential assigners so they match their parallel counterparts:
Iterator::cmp, no per-comparison allocation).assign()and run the existing logic over the uppercased UMIs (strand-assignment logic is unchanged; it now simply operates on the uppercased forms).This is a no-op for the uppercase UMIs the CLI emits, so it does not change CLI output on standard data.
Tests
test_sequential_and_parallel_{adjacency,edit,paired}_agree_on_mixed_case), comparing grouping structure (and, for paired, strand). Each fails onmainand passes here.test_adjacency_equal_count_tiebreak_is_case_insensitive).cargo ci-fmt,cargo ci-lint, andcargo ci-test(2210 tests) all pass.Reading order
crates/fgumi-umi/src/assigner.rs— the three sequential fixes + docs.src/lib/umi/parallel_assigner.rs— the four new tests (and a corrected stale "matches the sequential assigner exactly" comment).Summary by CodeRabbit
Bug Fixes
Tests