Repository navigation
feat(group): add --allow-unmapped flag for processing fully unmapped reads in fgumi group - #39
Conversation
ddd857f to
2a461c3
Compare
81b2cf6 to
3d638ff
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #39 +/- ##
==========================================
+ Coverage 83.88% 84.11% +0.23%
==========================================
Files 127 128 +1
Lines 51100 52082 +982
==========================================
+ Hits 42866 43810 +944
- Misses 8234 8272 +38 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
3d638ff to
b4b6005
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds two public BitEnc methods for 2-bit base access and substitution with bounds/value checks. Introduces an allow_unmapped flag to GroupReadsByUmi and threads it through filtering, grouping, MI-tagging, and logging to enable grouping of fully unmapped templates (requires query-name-sorted input when used). Exposes a new umi::parallel_assigner module providing a thread-safe UnionFind, parallel edge discovery, and ParallelIdentity/ParallelEdit/ParallelAdjacency/ParallelPaired assigners. Adds tests for the new behavior and a dev-only test dependency in the DNA crate. 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
crates/fgumi-dna/src/bitenc.rs (1)
261-268: Userstestfor parameterized cases intest_base_at.Manually asserting each position violates the "use rstest for parameterized tests" guideline. Consider:
♻️ Proposed refactor
+ use rstest::rstest; + - #[test] - fn test_base_at() { - let enc = BitEnc::from_bytes(b"ACGT").unwrap(); - assert_eq!(enc.base_at(0), 0); // A - assert_eq!(enc.base_at(1), 1); // C - assert_eq!(enc.base_at(2), 2); // G - assert_eq!(enc.base_at(3), 3); // T - } + #[rstest] + #[case(0, 0)] // A + #[case(1, 1)] // C + #[case(2, 2)] // G + #[case(3, 3)] // T + fn test_base_at(#[case] pos: usize, #[case] expected: u8) { + let enc = BitEnc::from_bytes(b"ACGT").unwrap(); + assert_eq!(enc.base_at(pos), expected); + }As per coding guidelines: "Use rstest for parameterized tests."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/fgumi-dna/src/bitenc.rs` around lines 261 - 268, Replace the manual assertions in the test_base_at function with an rstest parameterized test: keep constructing the BitEnc via BitEnc::from_bytes(b"ACGT").unwrap(), then parameterize inputs and expected outputs for base_at using rstest's #[rstest] attribute (e.g., rows for index and expected base) and assert that enc.base_at(index) == expected; update the test function signature (test_base_at) to accept the parameterized index and expected values and remove the four individual assert_eq! calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/fgumi-dna/src/bitenc.rs`:
- Around line 283-288: The test currently reads bases from enc2 but applies
with_base_at to enc, so it doesn't check idempotence for BitEnc; change the
mutation to use enc2 (i.e., call enc2.with_base_at(pos, base)) and assert the
result equals enc2 (or at least that restored.base_at(pos) == base) — update the
loop that calls BitEnc::from_bytes, base_at, and with_base_at to operate on enc2
instead of enc so the round-trip is properly tested.
In `@src/commands/group.rs`:
- Around line 1197-1217: The parallel branch under allow_unmapped currently
ignores the configured ThreadingOptions num_threads and uses rayon's global
pool; fix by making the parallel path respect num_threads: before creating
ParallelEditAssigner/ParallelAdjacencyAssigner/ParallelPairedAssigner, either
initialize/configure rayon's global pool from ThreadingOptions.num_threads (e.g.
via rayon::ThreadPoolBuilder with num_threads) or short-circuit to the
sequential path (call strategy.new_assigner_full(..., 1, index_threshold)) when
num_threads == 1; update the code where allow_unmapped, num_threads,
Parallel*Assigner::new and strategy.new_assigner_full are used so the chosen
assigner honors the --threads setting (and remove reliance on the deprecated
_threads parameter).
In `@src/lib/umi/parallel_assigner.rs`:
- Around line 469-624: The parallel assigner accepts malformed paired UMIs (no
dash or multiple dashes) whereas the sequential PairedUmiAssigner requires
exactly one dash; add an explicit validation at the start of
ParallelPairedAssigner::assign() that iterates raw_umis and asserts each
umi.split('-').count() == 2 (with a clear panic/error message referencing
ParallelPairedAssigner and paired UMI format) before calling
Self::canonicalize() or BitEnc::from_umi_str; this will make behavior consistent
with the sequential PairedUmiAssigner and avoid silently processing malformed
UMIs.
---
Nitpick comments:
In `@crates/fgumi-dna/src/bitenc.rs`:
- Around line 261-268: Replace the manual assertions in the test_base_at
function with an rstest parameterized test: keep constructing the BitEnc via
BitEnc::from_bytes(b"ACGT").unwrap(), then parameterize inputs and expected
outputs for base_at using rstest's #[rstest] attribute (e.g., rows for index and
expected base) and assert that enc.base_at(index) == expected; update the test
function signature (test_base_at) to accept the parameterized index and expected
values and remove the four individual assert_eq! calls.
📝 WalkthroughWalkthroughThis PR adds parallel UMI assignment capabilities and enhances bit-encoding operations. Changes include: two new 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@crates/fgumi-dna/src/bitenc.rs`:
- Around line 282-287: The test is using the wrong instance (`enc`) when
checking idempotence; change the restoration call to use the instance created
from bytes (`enc2`) so the mutation is exercised. Specifically, in the loop that
calls BitEnc::from_bytes and then uses base_at and with_base_at, replace the
call to enc.with_base_at(pos, base) with enc2.with_base_at(pos, base) (keeping
base_at and the subsequent assert the same) so restored is derived from enc2.
In `@src/commands/group.rs`:
- Around line 1197-1217: The parallel path for allow_unmapped creates
ParallelEditAssigner, ParallelAdjacencyAssigner, and ParallelPairedAssigner with
num_threads, but those constructors currently ignore the threads argument so
--threads has no effect; update each Parallel*Assigner::new implementation to
store and use the threads parameter (e.g., initialize the internal worker pool,
rayon thread pool, or spawn that many workers) or add a method to configure
thread count and call it from the call sites, ensuring
ParallelEditAssigner::new(effective_edits, num_threads),
ParallelAdjacencyAssigner::new(effective_edits, num_threads), and
ParallelPairedAssigner::new(effective_edits, num_threads) actually apply
num_threads; repeat the same fix for the identical code path at the other
location (lines referenced for the duplicate).
In `@src/lib/umi/parallel_assigner.rs`:
- Around line 492-527: The parallel assign path currently accepts malformed
paired UMIs; update ParallelPairedAssigner::assign to validate each raw_umi
before canonicalizing: for each umi in raw_umis, convert to uppercase and ensure
it contains exactly one dash (e.g., use Self::reverse_paired(&upper).is_some()
or split('-') and check parts.len()==2); if the check fails, skip that umi (or
increment an "invalid" counter/log) and do not call Self::canonicalize on it.
This keeps validation consistent with reverse_paired, canonicalize, and
matches_canonical and prevents malformed UMIs from being counted.
b4b6005 to
2ad1927
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/group.rs`:
- Around line 907-934: The code currently accepts queryname-sorted input when
self.allow_unmapped is set, but RecordPositionGrouper assumes
template-coordinate ordering and may mis-group mapped reads; update the check
around is_qname_sorted/is_tc_sorted to either reject queryname-sorted inputs
unless you verify all templates are unmapped or add a runtime guard: when
allow_unmapped && is_qname_sorted, scan a small sample of records (or use an
existing reader/flag) to detect any mapped read (e.g., non-unmapped
FLAG/REFERENCE fields) and if any mapped reads are present, bail with an
explicit error mentioning RecordPositionGrouper and suggesting
template-coordinate sort, otherwise keep the current acceptance path; reference
the symbols is_tc_sorted, is_qname_sorted, self.allow_unmapped and
RecordPositionGrouper to locate where to add the check/warning.
2ad1927 to
3691c0c
Compare
3691c0c to
de39ec6
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/umi/parallel_assigner.rs`:
- Around line 624-743: ParallelPairedAssigner is missing an explicit override of
split_templates_by_pair_orientation (PairedUmiAssigner provides false) so it
currently uses the trait default (true) and changes grouping behavior; add a
method impl on ParallelPairedAssigner named
split_templates_by_pair_orientation(&self) -> bool that returns false to match
PairedUmiAssigner and preserve existing grouping semantics (this will keep
behavior consistent with umi_for_read_impl and the sequential
PairedUmiAssigner).
---
Duplicate comments:
In `@src/commands/group.rs`:
- Around line 908-935: The code currently accepts queryname-sorted input when
self.allow_unmapped is true (is_qname_sorted), but RecordPositionGrouper assumes
template-coordinate ordering and will break if there are mapped reads; add a
runtime guard that rejects queryname-sorted input unless every template is
unmapped: when is_qname_sorted && self.allow_unmapped, scan the input records
(using the BAM/SAM reader and Record methods such as record.is_unmapped() or
reference_id() on each record) and if you encounter any mapped read
(reference_id != -1 or !is_unmapped()), bail with a clear error (similar style
to existing bail messages) instructing the user to template-coordinate sort or
remove mapped reads; reference the symbols is_qname_sorted, is_tc_sorted,
self.allow_unmapped, and RecordPositionGrouper (and its add_records/finish code
paths) so the check prevents handing mapped records to RecordPositionGrouper.
de39ec6 to
35d4c03
Compare
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/commands/group.rs`:
- Around line 908-935: When input is queryname-sorted and --allow-unmapped is
set, add a runtime guard to reject the file as soon as any mapped read is
observed because RecordPositionGrouper assumes template-coordinate order; update
the code path that constructs/feeds RecordPositionGrouper (or add the check
inside RecordPositionGrouper) to inspect records and bail with a clear error if
is_qname_sorted && self.allow_unmapped and a record with mapped flags (i.e., not
unmapped for its template) is encountered, referencing
is_template_coordinate_sorted, is_sorted/QUERY_NAME, self.allow_unmapped and
RecordPositionGrouper so callers know to either provide
template-coordinate-sorted BAM or ensure all templates are unmapped.
35d4c03 to
24bb0a2
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/group.rs`:
- Around line 891-898: The warning about paired-UMI edit semantics is only shown
for Strategy::Edit and Strategy::Adjacency but should also be shown for
Strategy::Paired; update the conditional guarding the warn(...) block (the
matches! check that currently references Strategy::Edit | Strategy::Adjacency)
to include Strategy::Paired so the message is emitted when self.strategy is
Strategy::Paired.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
crates/fgumi-dna/Cargo.tomlcrates/fgumi-dna/src/bitenc.rssrc/commands/group.rssrc/lib/umi/mod.rssrc/lib/umi/parallel_assigner.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/lib/umi/mod.rs
24bb0a2 to
da20643
Compare
da20643 to
349829c
Compare
449a288 to
80fc5e5
Compare
80fc5e5 to
30d5cfb
Compare
… UMI Adds --allow-unmapped flag to the group command, enabling UMI-based grouping of fully unmapped reads (e.g., ribosome display). When enabled, queryname-sorted input is also accepted. Includes parallel UMI assigners (identity, edit, adjacency, paired) optimized for the large single-position groups that arise when all reads are unmapped. Uses partition-merge for identity, parallel edge discovery with lock-free union-find for edit, and parallel edge discovery with sequential BFS for adjacency and paired strategies. Adds base_at/with_base_at methods to BitEnc for Hamming-distance neighbor enumeration.
30d5cfb to
b20b3cd
Compare
Summary
--allow-unmappedflag tofgumi groupcommand to support processing fully unmapped reads (both R1 and R2 unmapped)Changes
--allow-unmappedCLI flag toGroupReadsByUmistructallow_unmappedfield toGroupFilterConfigfilter_template()to conditionally allow unmapped templates when flag is set--allow-unmappedis enabledfilter_templatewith unmapped templatesparallel_assignermodule withParallelEditAssignerandParallelAdjacencyAssignerBitEnc::with_base_at()andbase_at()methods for neighbor UMI generation--allow-unmappedis usedParallel UMI Assigner
When
--allow-unmappedis enabled, all unmapped reads form a single position group which can contain millions of unique UMIs. The standard O(U²) algorithms become bottlenecks.The new parallel assigner uses:
This provides 10-100x speedup for large unmapped datasets.
Behavior
When
--allow-unmappedis enabled:Important Notes
ACGT-TGCA), edit distance is computed on the concatenated sequence with dashes removed. With--edits 1, only 1 mismatch is allowed across ALL bases (30 bases for 15bp-15bp paired UMIs).Test plan
filter_templateallowing/rejecting unmapped templatesallow_unmapped: falseBitEnc::with_base_at()andbase_at()methods