Skip to content

fix(consensus)!: clamp codec/duplex scalar depth+error tags to fgbio's Short ceiling - #552

Merged
nh13 merged 1 commit into
mainfrom
nh/fix-consensus-depth-clamp-parity
Jul 17, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/fix-consensus-depth-clamp-parity

Conversation

@nh13

@nh13 nh13 commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

What

Clamp the scalar consensus depth tags (aD/bD/cD and the aM/bM/cM minima) and the error-rate tags (aE/bE/cE — both the depth denominators and the error numerators) to fgbio's Short ceiling (i16::MAX = 32767) in the codec and duplex callers, matching what the vanilla (simplex) caller already does and what fgbio emits. This makes all three consensus callers consistent with each other and bit-identical to fgbio.

Why

fgbio stores per-base consensus depth and per-base error counts as Array[Short] capped at Short.MaxValue and derives every scalar depth/error-rate tag from those capped arrays. fgumi keeps per-base depth and errors in u16. The vanilla caller already matches fgbio by clamping both depths and errors at push (with a test), and base_builder.rs documents the intent to clamp "at tag emission" — but the codec and duplex callers emitted the scalar depth and error-rate tags uncapped, so they diverged from fgbio on families deeper than 32767 reads (e.g. measured aD 59034 vs 32767). The three callers were inconsistent with each other and with fgbio, and this had been re-litigated repeatedly (the per-base arrays were hardened before, but the scalars were left uncapped).

How

  • Add a shared clamp_per_base_to_fgbio_short(u16) -> i32 helper.
  • codec/duplex: clamp each per-base depth before max/min/sum. For the combined duplex/codec cD/cM, cap each strand's per-base value before summing, exactly as fgbio's totalDepths(i) = min(abD_i, 32767) + min(baD_i, 32767) — so a mixed pair where only one strand saturates yields the correct intermediate value (e.g. 62767), not an uncapped sum.
  • Cap the error-rate numerators the same way: aE/bE/cE are now summed from per-base errors capped at the ceiling, matching fgbio's capped errors Array[Short]. Both the numerator and the denominator of every error rate now derive from Short-capped per-base values.
  • Only the emitted tags are capped; the unclamped depth is still used for the --min-reads threshold, so no behavior is lost.

Tests

  • Helper case table (in-range / at-ceiling / above / u16::MAX).
  • Duplex + codec depth-saturation tests covering the intermediate cD case (aD→32767, bD→30000, cD→62767), alongside the existing vanilla saturation test.
  • Duplex + codec error-rate numerator-cap tests: a per-base error above the ceiling yields the capped rate (aE = 32767/(32767·4) = 0.25) rather than the uncapped value (0.3052); verified to fail before the numerator fix.

Notes

  • Breaking (!): changes the emitted depth- and error-rate-tag values for consensus families deeper than 32767 reads (now capped, matching fgbio).
  • Pairs with the compare-tool change on nh/compare-hardening (feat(compare): harden fgumi compare into a sound, faithful fgbio-parity oracle #530), which drops the now-unnecessary depth-saturation tolerance and compares these tags exactly. With the error numerators now capped too, the tolerance can be dropped for aE/bE/cE as well, not just the depth tags.

Summary by CodeRabbit

  • Bug Fixes

    • Updated consensus depth and error calculations to respect fgbio’s Short ceiling before computing SAM scalar and per-base tags.
    • Prevented overflow/wraparound in very deep coverage scenarios, including duplex and strand-level depth/error-rate outputs.
    • Ensured duplex totals and error-rate numerators/denominators are derived from capped per-strand/per-base inputs for consistent tag behavior.
  • Tests

    • Added regression tests for saturation edge cases (including mixed-saturation) and for overflow-safe error-rate denominator computation.
    • Added offline “fgbio oracle” saturation comparisons against pinned BAM captures.

@nh13
nh13 temporarily deployed to github-actions July 10, 2026 18:45 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 36c627b7-9666-4db8-bcaf-25ee3b5bee9d

📥 Commits

Reviewing files that changed from the base of the PR and between 11dff2d and e268c6c.

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

Walkthrough

Per-base depths and errors now cap at fgbio’s Short ceiling before scalar aggregation, duplex summation, and signed-array encoding. CODEC and duplex SAM tags use cap-before-sum semantics, with regression and oracle tests covering saturation and overflow prevention.

Changes

Depth saturation

Layer / File(s) Summary
Depth clamp contract
crates/fgumi-consensus/src/caller.rs
Defines the public Short ceiling and clamp helper, with boundary and saturation tests.
CODEC depth aggregation
crates/fgumi-consensus/src/codec_caller.rs
Caps strand depths and errors before duplex aggregation and scalar tag calculations, saturates per-base i16 arrays, and tests deep-family behavior.
Duplex depth tags
crates/fgumi-consensus/src/duplex_caller.rs
Applies per-strand clamping to depth and error tags, combined metrics, and regression cases for asymmetric saturation.
Oracle validation support
crates/fgumi-consensus/Cargo.toml, crates/fgumi-consensus/src/lib.rs, crates/fgumi-consensus/src/oracle_test_support.rs, crates/fgumi-consensus/src/codec_caller.rs, crates/fgumi-consensus/src/duplex_caller.rs
Adds test-only BAM round-tripping, pinned tag assertions, fixture regeneration, and offline fgbio saturation comparisons.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: clamping codec and duplex scalar depth/error tags to fgbio's Short ceiling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nh/fix-consensus-depth-clamp-parity

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

@codecov

codecov Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.89%. Comparing base (f55ac4b) to head (e268c6c).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
crates/fgumi-consensus/src/duplex_caller.rs 93.62% 13 Missing ⚠️
crates/fgumi-consensus/src/oracle_test_support.rs 78.18% 12 Missing ⚠️
crates/fgumi-consensus/src/codec_caller.rs 96.51% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #552      +/-   ##
==========================================
+ Coverage   92.84%   92.89%   +0.05%     
==========================================
  Files         166      167       +1     
  Lines      102064   102777     +713     
==========================================
+ Hits        94765    95479     +714     
+ Misses       7299     7298       -1     

☔ 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 Jul 16, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 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 commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 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 force-pushed the nh/fix-consensus-depth-clamp-parity branch from 14a1cd8 to c3131c5 Compare July 16, 2026 15:31
@nh13
nh13 temporarily deployed to github-actions July 16, 2026 15:31 — with GitHub Actions Inactive
@nh13 nh13 changed the title fix(consensus)!: clamp codec/duplex scalar depth tags to fgbio's Short ceiling fix(consensus)!: clamp codec/duplex scalar depth+error tags to fgbio's Short ceiling Jul 16, 2026
@nh13
nh13 force-pushed the nh/fix-consensus-depth-clamp-parity branch from c3131c5 to 11dff2d Compare July 16, 2026 19:46
@nh13
nh13 temporarily deployed to github-actions July 16, 2026 19:46 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-consensus/src/codec_caller.rs`:
- Around line 3224-3330: Add generated fgbio-oracle coverage for the saturation
contract in crates/fgumi-consensus/src/codec_caller.rs lines 3224-3330: replace
or supplement the hand-authored formula tests with a programmatically generated
deep CODEC fixture and assert emitted scalar and per-base tags match fgbio. Add
the equivalent generated fixture in crates/fgumi-consensus/src/duplex_caller.rs
lines 5028-5176, covering mixed strand saturation and capped error numerators;
both sites must assert identity with the fgbio baseline or document any
intentional divergence.
- Around line 1350-1364: Change the combined-depth accumulation in the consensus
error-rate calculation around total_depths and total_bases to use i64, matching
the duplex implementation. Ensure the per-base depth values are converted before
summing and adjust the division types as needed so deeply covered consensuses
cannot overflow while preserving the existing zero-depth behavior.
🪄 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: b9a82a46-0dec-4261-80c6-d6f2a11ac53b

📥 Commits

Reviewing files that changed from the base of the PR and between 14a1cd8 and 11dff2d.

📒 Files selected for processing (3)
  • crates/fgumi-consensus/src/caller.rs
  • crates/fgumi-consensus/src/codec_caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs

Comment thread crates/fgumi-consensus/src/codec_caller.rs
Comment thread crates/fgumi-consensus/src/codec_caller.rs
…s Short ceiling

fgbio stores per-base consensus depth AND error counts as `Array[Short]` capped at
`Short.MaxValue` (32767) and derives every scalar depth/error-rate tag from those capped
arrays. fgumi keeps per-base depth and errors in `u16` (up to 65535). The simplex (vanilla)
caller already matches fgbio by clamping both depths and errors at push, but the codec and
duplex callers emitted the scalar `aD`/`bD`/`cD` (plus the `aM`/`bM`/`cM` minima and the
`aE`/`bE`/`cE` error rates) uncapped, so they diverged from fgbio on families deeper than
32767 reads. The three callers were therefore inconsistent with each other and with fgbio.

Clamp each per-base depth to the Short ceiling before `max`/`min`/sum, via a shared
`clamp_per_base_to_fgbio_short` helper. For the combined duplex/codec `cD`/`cM`, cap each
strand's per-base value *before* summing, exactly as fgbio's
`totalDepths(i) = min(abD_i, 32767) + min(baD_i, 32767)`, so a mixed pair where only one
strand saturates yields the correct intermediate value (e.g. 62767), not an uncapped sum.
Cap the error-rate numerators the same way, so `aE`/`bE`/`cE` are summed from per-base
errors capped at the ceiling, matching fgbio's capped `errors` `Array[Short]` -- both the
numerator and denominator of the rate now derive from Short-capped per-base values.

Only the emitted tags are capped; the unclamped depth is still used for the min-reads
threshold, so no behavior is lost. Adds a helper case table, duplex/codec depth-saturation
tests covering the intermediate `cD` case, and duplex/codec error-rate numerator-cap tests,
alongside the existing vanilla saturation test.
@nh13
nh13 force-pushed the nh/fix-consensus-depth-clamp-parity branch from 11dff2d to e268c6c Compare July 17, 2026 14:16
@nh13
nh13 temporarily deployed to github-actions July 17, 2026 14:16 — with GitHub Actions Inactive
@nh13
nh13 merged commit efdceef into main Jul 17, 2026
10 checks passed
@nh13
nh13 deleted the nh/fix-consensus-depth-clamp-parity branch July 17, 2026 14:20
@nh13 nh13 mentioned this pull request Jul 17, 2026
nh13 added a commit that referenced this pull request Jul 17, 2026
…tore

The CODEC caller's per-base combined duplex error was `ea + eb` (and the
disagreement-branch sums) on `u16`. For very deep families where both strands
carry a high per-base error count, `40000 + 40000 = 80000` exceeds `u16::MAX`,
which panics in debug and wraps in release BEFORE `cE` clamps it. This is the
error-count analogue of the depth cap-before-sum fix in #552 (which only capped
the per-base depth); the error sum was left uncapped.

Sum each per-base error in `u32` and saturate at fgbio's `Short` ceiling via a
shared `clamp_duplex_error_to_fgbio_short` helper, matching fgbio's per-base
`errors` `Array[Short]` (capped at 32767 at storage). The downstream `cE`
numerator re-clamps to the same ceiling, so this changes only the stored
intermediate, never the emitted tag. The duplex caller's analogous `error_at_i`
already sums in `i32` and clamps to `i16::MAX`, so it was unaffected.

Adds `test_duplex_per_base_error_caps_before_summing`, the error-array companion
of the existing per-base depth cap-before-sum test, driving
`build_duplex_consensus_from_padded` with both strands agreeing and each
carrying a per-base error above the ceiling (which panicked in debug before the
fix).

This branch was previously deployed

1 inactive deployment
github-actions — e268c6c2 Deployed Jul 17, 2026 by nh13 via coverage #2646
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