Skip to content

test(consensus): pin read-order independence of alignment grouping - #1037

Merged
nh13 merged 1 commit into
mainfrom
nh/cmp-cigar-order-independence
Oct 8, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/cmp-cigar-order-independence

Conversation

@nh13

@nh13 nh13 commented Oct 7, 2026

Copy link
Copy Markdown
Member

Summary

The fgbio parity audit reported that the alignment-group CIGAR comparator (cmp_cigar in select_most_common_alignment_group) depends on input order: when the reads arrive longest-first, a prefix CIGAR was said to be absorbed into the longer CIGAR's group. That does not reproduce, so this PR adds tests only and changes no production code.

Grouping does not depend on input order:

  • filter_source_reads_by_alignment (and the CODEC caller) sort reads by descending query length with a stable sort before grouping, as fgbio's filterToMostCommonAlignment does with sortBy(-length).
  • A read joins every group whose CIGAR it is a prefix of, with no early break, as fgbio does. is_cigar_prefix matches fgbio's Cigar.isPrefixOf.
  • cmp_cigar orders element by element (length, then operator: M < I < D < N < S < H < P < = < X), then by element count. That is fgbio's Cigar.cigarOrdering, and max_by(size).then(cmp_cigar(b, a)) matches maxBy((size, cigar))(Ordering.Int, cigarOrdering.reverse). Group CIGARs are distinct, so this is a total order with no ties, and the winning group cannot depend on input order.

No output change.

fgbio parity (e51a661)

  • UmiConsensusCaller.scala:406-436 (filterToMostCommonAlignment): stable length sort, no-break prefix grouping, and the size-then-smallest-CIGAR tie-break that fgumi implements.
  • Alignment.scala:57,102-112 (cigarElemOrdering, cigarOrdering): length before operator, then element count.
  • fgumi had no ports of fgbio's four tie-breaking tests. They are added with fgbio's orderings, winners and group sizes: VanillaUmiConsensusCallerTest.scala:583, :608, :630 and :652.

One quirk is shared with fgbio and not pinned: reads with the same query length whose CIGARs are prefix-related (e.g. 25M and 25M1D) can group differently depending on input order, because the first such read seeds the group.

Out of scope: for hard-clipped records, fgumi simplifies the CIGAR before truncating it and fgbio truncates first, so the two tools can group such reads differently. That divergence is fixed in a separate PR.

Tests

All in crates/fgumi-consensus/src/vanilla_caller.rs, under the existing filterToMostCommonAlignment section:

  • test_filter_tied_alignment_groups_select_same_winner_in_any_input_order: the four fgbio ports, plus three cases whose tied groups share a query length, so their orderings create the groups in opposite orders: smaller first element, I before D, and element length before operator (25M1D25M beats 25M2I23M).
  • test_filter_tied_prefix_groups_prefer_fewer_elements: [25M, 25M1D] keeps 25M, pinning the element-count tie-break (fgbio's :630 port is decided by the first element instead).
  • test_filter_prefix_cigars_group_identically_in_any_input_order: six prefix-related reads in all 720 input orders (via itertools::permutations), asserting the same kept and rejected ids every time.
  • test_consensus_with_prefix_cigars_is_identical_in_any_input_order: end to end, asserting the consensus base that identifies the winning group, the per-base and max depth, and byte-identical output across orders.

itertools moves to [workspace.dependencies], since it is now used by two members, and is added as a dev-dependency of fgumi-consensus.

Mutation-checked: each of these fails at least one new test: flipping or removing the element-count tie-break, comparing operator before length, reversing the element-length or operator order, swapping the I/D ordinal, dropping or reversing the CIGAR tie-break, removing or reversing the length sort, and stopping at the first matching group.

Checks

cargo ci-fmt, cargo ci-lint, cargo ci-doc, cargo ci-doctest, cargo ci-tag-literals and cargo ci-test all pass.

The fgbio audit flagged the alignment-group CIGAR comparator as
order-dependent: with input sorted by length descending, a prefix CIGAR
was said to be absorbed into the longer CIGAR's group. This does not
reproduce. Grouping runs over a stable descending-length sort of the
reads, as fgbio's filterToMostCommonAlignment does (sortBy -length). A
prefix CIGAR is never longer than the CIGARs it prefixes, and it is added
to every matching group without breaking early. The comparator only
breaks size ties between groups, and it is a total order over distinct
CIGARs, so it cannot depend on input order either.

One case is order-dependent in both tools and is not pinned: a prefix
of equal query length (its extension consumes no query, e.g. 25M vs
25M1D) ties in the stable sort, so input order decides which read seeds
the group. Hard-clipped records can also group differently from fgbio,
because fgumi simplifies the CIGAR before truncating it; that is out of
scope here.

Add regression tests:
- ports of fgbio's four tie-breaking tests
  (VanillaUmiConsensusCallerTest.scala:583, :608, :630, :652), which
  fgumi did not have
- equal-query-length tie cases whose input orders create the groups in
  opposite orders, including one where element length and operator
  disagree, pinning length-before-operator priority
- a fixed-order 25M vs 25M1D case pinning the element-count tie-break
- six prefix-related reads in all 720 input orders, asserting the same
  kept and rejected read ids, with kept reads in input order
- an end-to-end consensus over three of those orders, asserting the
  consensus base that identifies the winning group, the per-base and
  maximum depth, and byte-identical output

Move itertools to the workspace dependencies so fgumi-consensus can use
its permutations in tests.

No output change.
@nh13
nh13 deployed to github-actions October 7, 2026 07:31 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

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

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: 3a635967-601a-40e2-b1cf-1ed4d1ba0afb
📥 Commits

Reviewing files that changed from the base of the PR and between 62b98c0 and 622729f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml
  • crates/fgumi-consensus/Cargo.toml
  • crates/fgumi-consensus/src/vanilla_caller.rs

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


⚠️ A high-level summary could not be generated for this review. CodeRabbit will regenerate it on the next update, or you can request a refresh with @coderabbitai summary.

Walkthrough

No production behavior change is shown. The change centralizes the itertools dependency and adds tests for deterministic CIGAR alignment-group selection and consensus output.

Changes

CIGAR Selection Tests

Layer / File(s) Summary
Tie selection tests
Cargo.toml, crates/fgumi-consensus/Cargo.toml, crates/fgumi-consensus/src/vanilla_caller.rs
The workspace declares itertools, and the consensus crate uses it for development. Tests check tied-group selection, including tie-breaks based on CIGAR element length, operator order, and element count.
Permutation and consensus checks
crates/fgumi-consensus/src/vanilla_caller.rs
Tests check the winning group and retained input order across all 720 permutations of six reads. End-to-end tests compare consensus bases, depth tags, and output bytes across three input orders.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other

Merge Risk: ⚪ Minimal · up to 62272

The added tests and dependency declarations present no identified obstacle to merging 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. The test(consensus) scope identifies the affected area, and the lowercase imperative description summarizes the tests for read-order-indepe…
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 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.48%. Comparing base (62b98c0) to head (622729f).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1037   +/-   ##
=======================================
  Coverage   96.47%   96.48%           
=======================================
  Files         299      299           
  Lines      152214   152288   +74     
=======================================
+ Hits       146854   146928   +74     
  Misses       5360     5360           

☔ 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.

@nh13

nh13 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 7, 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.

@nh13
nh13 added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit ffccb49 Oct 8, 2026
21 checks passed
@nh13
nh13 deleted the nh/cmp-cigar-order-independence branch October 8, 2026 00:57
@nh13 nh13 mentioned this pull request Oct 8, 2026

This branch was successfully deployed

1 active deployment
github-actions — 622729f0 Deployed Oct 7, 2026 by nh13 via coverage #4853
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