feat(simulate): body error injection, template-coordinate sort fix, correctness guards (SIMU3-01/03/04/05/07) - #541
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 15 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughSimulation commands add configurable read-body substitution errors, preserve UMI prefixes, align mapped sort keys with effective emitted loci, constrain duplex partitioning, and improve short-UMI correction modeling. ChangesSimulation output generation
UMI correction simulation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant GenerationParams
participant ReadGenerator
participant introduce_errors_inplace
participant FASTQOutput
CLI->>GenerationParams: validate and store error_rate
GenerationParams->>ReadGenerator: generate molecule reads
ReadGenerator->>introduce_errors_inplace: mutate read bodies when error_rate > 0
introduce_errors_inplace-->>ReadGenerator: substituted sequences
ReadGenerator-->>FASTQOutput: fixed-length R1/R2 reads
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
b4f1e4e to
46d0a9a
Compare
|
Both reviewers run (§0 step 6). CodeRabbit CLI (
Local CR-style review — no actionable findings; one nitpick (the correctable radius/min-distance in
Commit is unsigned (1Password unavailable); will be re-signed + force-pushed once signing returns. |
46d0a9a to
c6359c7
Compare
|
Follow-up on the grouped-reads note in the PR body: I investigated it and it's not a bug — retracting the "separate pre-existing gap" framing. The generated grouped-reads output has 0 descending steps in the template primary key (verified both without any fallback and on a fallback-heavy reference, 12,066 records each) — i.e., it is validly template-coordinate sorted. The earlier "68/3059 records differ from For contrast, the mapped-reads SIMU3-05 bug state (pre-sampled sort key) had 36 genuine descending steps → 0 after the fix, which is why mapped is a real S1 and grouped is not. No grouped change is needed; scoping SIMU3-05 to mapped-reads was correct, and there is no residual follow-up here. |
c6359c7 to
703d2f6
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #541 +/- ##
==========================================
+ Coverage 92.73% 92.80% +0.07%
==========================================
Files 166 166
Lines 101603 101888 +285
==========================================
+ Hits 94218 94557 +339
+ Misses 7385 7331 -54 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Overlap with #576 (simulate template-coordinate sort)#576 takes a different, broader approach to the sort ordering: it deletes simulate's bespoke key ( Because it sorts the emitted records, #576:
Proposal: let #576 own the simulate sort ordering, and rebase this PR to drop the SIMU3-05 sort portion — keeping its genuinely separate improvements: body error injection (SIMU3-01), two-stranded duplex families (SIMU3-07), truth-file verification (SIMU3-03), and the short-UMI panic guard (SIMU3-04). They don't overlap #576 and are worth keeping. (These edit the same |
703d2f6 to
5784271
Compare
|
@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 `@src/lib/commands/simulate/fastq_reads.rs`:
- Around line 539-555: Isolate body-error randomness from molecule-generation
randomness by using deterministic, independent per-read/per-mate RNG streams for
introduce_errors_inplace in src/lib/commands/simulate/fastq_reads.rs:539-555,
and apply the same change to grouped_reads.rs:559-581 and
mapped_reads.rs:473-497 so qualities and later family generation remain
unchanged. Extend the assertions in fastq_reads.rs:1074-1147 to preserve UMI
prefixes, strand/truth fields, and qualities; in grouped_reads.rs:1020-1065
preserve non-sequence fields and qualities; and in mapped_reads.rs:1089-1145
preserve positions, flags, and qualities.
In `@src/lib/commands/simulate/grouped_reads.rs`:
- Around line 244-249: Update grouped-reads ordering to account for the fallback
locus generated later in the molecule flow: re-key fallback molecules using
effective_molecule_locus before sorting, matching the mapped-reads behavior, or
pass emitted records through the canonical sorter. Remove or revise the nearby
template-coordinate ordering note so it no longer advertises behavior the
implementation does not guarantee.
🪄 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: 8102d586-abcc-495b-9cd2-c6d06313e732
📒 Files selected for processing (5)
src/lib/commands/simulate/common.rssrc/lib/commands/simulate/correct_reads.rssrc/lib/commands/simulate/fastq_reads.rssrc/lib/commands/simulate/grouped_reads.rssrc/lib/commands/simulate/mapped_reads.rs
5784271 to
d780822
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/lib/commands/simulate/grouped_reads.rs`:
- Around line 415-421: Add a focused test for the duplex grouping path using
family_size >= 2, preferably exactly 2, and iterate across multiple RNG seeds;
assert the resulting split from split_reads_with_minimum (or the emitted
MI-tagged pairs) always contains at least one A read and one B read. Keep the
existing orientation-flip test unchanged and target the SIMU3-07
minimum-per-strand guarantee directly.
🪄 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: 9b16cd0e-a0d6-4131-8c60-e2fe510d5585
📒 Files selected for processing (5)
src/lib/commands/simulate/common.rssrc/lib/commands/simulate/correct_reads.rssrc/lib/commands/simulate/fastq_reads.rssrc/lib/commands/simulate/grouped_reads.rssrc/lib/commands/simulate/mapped_reads.rs
…nd correctness guards W7 of the final-audit burn-down: makes the simulate generators produce realistic, internally-consistent test data (SIMU3-01/03/04/05/07). - SIMU3-01: add a `--error-rate` flag (default 0.0) to mapped-reads, grouped-reads, and fastq-reads that injects per-base substitution errors into read bodies. The guard on `error_rate > 0.0` means the default draws no RNG and leaves output byte-identical to the error-free generator; a positive rate gives consensus and error-correction paths genuine discordances to resolve. The shared `introduce_errors_inplace` helper moves to `common.rs`. fastq-reads gains body injection beyond the existing UMI-prefix perturbation. - SIMU3-05 (mapped-reads): the template-coordinate sort-key pre-pass keyed on the pre-sampled locus, but generation re-samples a new locus when the pre-sampled one is too close to a contig end for the drawn insert size, so records were emitted out of `SS:template-coordinate` order. A shared `effective_molecule_locus` replays the molecule RNG through the same re-sample fallback so the key uses the emitted coordinates. (grouped-reads has a separate, pre-existing sort-order gap and is intentionally left for a follow-up rather than conflated here.) - SIMU3-07 (grouped-reads): duplex families used `split_reads`, which could leave a strand with zero reads. Switch to `split_reads_with_minimum(family_size, 1)` so a duplex family with >= 2 reads is genuinely two-stranded (same RNG consumption). - SIMU3-03 (correct-reads): the truth file assumed edit1/edit2 always correct to the true UMI. Compute the expected correction from the actual nearest includelist neighbor (`expected_correction_for`) so a nearest-neighbor collision is reported as uncorrectable, matching `fgumi correct`. - SIMU3-04 (correct-reads): guard the "multi" error branch so a UMI shorter than 3 bases no longer panics on an inverted `random_range(3..=umi_length.min(5))`. Verified end-to-end with the built binary: mapped-reads output is byte-for-byte `samtools sort --template-coordinate` (RED: pre-sampled key left 4238/11988 records out of order); `--error-rate 0.05` changes bases but not positions/flags; duplex families are two-stranded; and correct-reads with `--umi-length 2` no longer panics. CI green (2259 default, 2150 with --features simulate).
d780822 to
9225518
Compare
Summary
W7 of the final-audit burn-down: the
simulateerror-model / fixture-generator cluster.simulateis fgumi-specific (no fgbio oracle), so verification is against the generator's own advertised contract — template-coordinate sorted output, two-stranded duplex families, and a truth file that agrees withfgumi correct. Stacked on #531 (nh/simulate-ss-parity), the only open PR touchingmapped_reads.rs/grouped_reads.rs; retargets tomainon merge.Findings
SIMU3-01 (S2) — no body sequencing errors
The generators emitted error-free reads (only the fastq UMI prefix was perturbed), so consensus/error-correction paths never saw real discordances. Added a
--error-rateflag (default 0.0) to mapped-reads, grouped-reads, and fastq-reads that injects per-base substitution errors into read bodies via a sharedintroduce_errors_inplace(moved tocommon.rs). Theerror_rate > 0.0guard is important:introduce_errors_inplacedraws one RNG value per base regardless of rate, so guarding keeps the default byte-identical to the error-free generator. Per the maintainer's call, this ships opt-in/off-by-default so the ~12 E2E regression baselines are unchanged; a follow-up can opt specific fixtures into a positive rate.SIMU3-05 (S1) — sort-order break near contig ends (mapped-reads)
The template-coordinate sort-key pre-pass keyed on the pre-sampled locus, but generation re-samples a new locus when the pre-sampled one is too close to a contig end for the drawn insert size — so records were emitted out of the advertised
SS:template-coordinateorder. A sharedeffective_molecule_locusreplays the molecule RNG (UMI → family size → insert size → strand coin →sequence_at/sample_sequencefallback) so the key uses the emitted coordinates. For loci that don't trigger the fallback the key is unchanged.Scope: the audit scoped SIMU3-05 to mapped-reads, and this fixes it there. grouped-reads has a separate, pre-existing template-coordinate ordering gap (present even without any fallback), so it is deliberately left for a follow-up rather than conflated here — the grouped sort key is unchanged.
SIMU3-07 (data quality) — duplex families not two-stranded
grouped-reads
--duplexusedsplit_reads, which can assign zero reads to a strand. Switched to the existingsplit_reads_with_minimum(family_size, 1)(identical RNG consumption — a singlesample_a_fractiondraw), so a duplex family with ≥ 2 reads is genuinely two-stranded.family_size == 1is inherently single-stranded and falls back.SIMU3-03 (S3) — truth file unverified vs nearest-neighbor collisions
correct-reads asserted edit1/edit2 → true UMI without checking whether the observed UMI is actually nearest to a different includelist entry.
expected_correction_fornow computes the unique nearest includelist neighbor within the correctable radius (mirroringfgumi correctwith max-mismatches 2 / min-distance 1), reporting collisions/ambiguous cases as uncorrectable so the truth agrees withcorrect.SIMU3-04 (S3) — panic for short UMIs
rng.random_range(3..=umi_length.min(5))inverts (and panics) whenumi_length < 3. Guarded so the "multi" branch clamps the upper bound instead.Verification (generator's own contract, via the built binary)
samtools sort --template-coordinate. RED: reverting the key to the pre-sampled locus left 4238/11988 records out of order.--error-rate 0.05changes 1191 sequences vs0.0, with positions/flags/names identical (errors don't move alignments).grouped-reads --duplex→ 290/300 families two-stranded (the 10 singletons arefamily_size == 1).correct-reads --umi-length 2exits 0 (no panic).Tests
common.rs:effective_molecule_locuspassthrough + contig-end fallback.correct_reads.rs:expected_correction_for(unique-nearest, collision, beyond-radius, exact) + a short-UMI no-panic test.cargo ci-fmt && ci-lint && ci-test(2259 default) andcargo nextest run --features simulate(2150).Follow-ups
--error-rate.(This commit was pushed unsigned because 1Password signing was unavailable; it will be re-signed and force-pushed once signing is back.)
Summary by CodeRabbit
--error-ratetofastq-reads,grouped-reads, andmapped-reads.--error-rate=0.0keeps output byte-identical to previous versions.