true_vaf_A1 <- 0.50
hop_rate <- 0.01
apparent_vaf_A2 <- true_vaf_A1 * hop_rate
apparent_vaf_A2#> [1] 0.005
1. Fragmentation (mechanical or enzymatic) → keep inserts within high-quality read lengths (~200–500 bp) because read quality decays after ~300 cycles.
2. End-repair & A-tailing → produce blunt, 5′-phosphorylated, 3′-adenylated ends so one ligation chemistry works for diverse fragments.
3. Adaptor ligation → introduces common priming sites (P5/P7, Rd1/Rd2 SP) and sample indices.
4. Library amplification → boosts low-input material and completes full-length primer/flow-cell motifs.
5. Sequencing → pooled, indexed libraries bind the flow cell (P5/P7), enabling multiplexing.
CDI enables large multiplexing but exposes samples to misassignment if indices hop. UDI reduces this risk.
Back-of-the-envelope effect of index hopping
If sample A1 has a true variant at 50% VAF and A2 is wild-type (0% VAF), and 1% of A1 reads hop into A2, the apparent VAF in A2 is ~0.5% — potentially above rare-variant thresholds.
true_vaf_A1 <- 0.50
hop_rate <- 0.01
apparent_vaf_A2 <- true_vaf_A1 * hop_rate
apparent_vaf_A2#> [1] 0.005
Implication: For ultra-rare calls (≤0.5%), UDI and stringent demultiplexing are essential.
• Pre-ligation damage: deamination/depurination/mismatches on one strand.
• End-repair fill-in: polymerase misincorporation, slippage, template switching.
• PCR & sequencing: base substitution/indel/phasing.
• Ligation artifacts: misligation, concatemers.
Most events hit one strand; seeing the same complementary change on both strands at the same position is improbably rare unless a true variant is present.
UMI-labeled adaptors mark original molecules and strand of origin (Read1 UMI = A, Read2 UMI = B → top vs. bottom). Workflow: 1. Build single-strand consensus sequences (SSCS) by collapsing UMI families on each strand. 2. Build a duplex consensus sequence (DCS) by comparing SSCS pairs from complementary strands.
Minority errors are filtered within SSCS; majority errors that dominated one strand are filtered when forming the DCS — provided the complementary strand is unaffected.
Expected error scale if errors are independent
Let per-base error probability on each strand be p. Requiring concordant complementary changes implies error ≈ p^2. If typical polymerase+sequencer error is ~10^-3 to 10^-4, a naïve expectation for DCS error is 10^-6 to 10^-8. Empirically, many DS implementations report ≈10^-7 due to non-independent errors (e.g., single-strand damage copied during end-repair).
p <- c(1e-3, 3e-4, 1e-4)
tibble(p = p, expected_DCS_error = p^2)#> # A tibble: 3 × 2
#> p expected_DCS_error
#> <dbl> <dbl>
#> 1 0.001 0.000001
#> 2 0.0003 0.00000009
#> 3 0.0001 0.00000001
If a damaged base on one strand is converted to a stable change on both strands during end-repair/fill-in, DCS will accept it — it mimics a true variant. Thus, reducing or eliminating such conversions is pivotal.
NanoSeq uses enzymatic blunt-ending and dideoxy-nucleotides (ddBTPs) to arrest end-repair, minimizing the copying of single-strand lesions into duplex artifacts. With UDI + UMI + DCS, the overall error rate drops toward ≤5×10^-9 in practice when sufficient coverage and stringent filters are applied.
Below, we reproduce the deck’s throughput estimates for a 40 kb mouse CHIP panel and the coverage required to detect variants appearing once at duplex level.
Assumptions (adjust as needed): • Duplex coverage target ≈ 665× (sufficient to see a one-time DCS event with high confidence) • Raw (pre-consensus) coverage per bp ≈ 21,000× • Read length chemistry: 2×150 (≈300 cycles per paired read) • Capture efficiency ≈ 70% • NovaSeq 25B yields: specify reads per flow cell (e.g., 15–20 billion)
panel_bp <- 4e4 # 40 kb
raw_cov_per_bp <- 2.1e4 # 21,000× raw coverage per bp
cycles_per_PE_read <- 300 # 2x150
capture_efficiency <- 0.70 # 70%
cycles_per_sample <- panel_bp * raw_cov_per_bp
reads_per_sample <- cycles_per_sample / cycles_per_PE_read
reads_per_sample_adj<- reads_per_sample / capture_efficiency
# Illustrate with two plausible flow-cell yields
flowcell_reads_low <- 1.5e10
flowcell_reads_high <- 2.0e10
samples_low_yield <- floor(flowcell_reads_low / reads_per_sample_adj)
samples_high_yield <- floor(flowcell_reads_high / reads_per_sample_adj)
tribble(
~quantity, ~value,
"Cycles per sample", scales::comma(cycles_per_sample),
"Reads per sample (raw)", scales::comma(reads_per_sample),
"Reads per sample (70%)", scales::comma(reads_per_sample_adj),
"Samples per 25B (15B)", scales::comma(samples_low_yield),
"Samples per 25B (20B)", scales::comma(samples_high_yield)
) %>% knitr::kable()| quantity | value |
|---|---|
| Cycles per sample | 840,000,000 |
| Reads per sample (raw) | 2,800,000 |
| Reads per sample (70%) | 4,000,000 |
| Samples per 25B (15B) | 3,749 |
| Samples per 25B (20B) | 4,999 |
Rule-of-thumb costs (example): if library prep ≈ $32/sample and a 25B flow cell ≈ $20,000, sequencing cost per sample ≈ $3 at the high-multiplex end, for a total of ≈ $35 per sample (panel-scale). Adjust for your actual yields and kit pricing.
This stylized simulation generates two independent read families (top/bottom) with per-base error p. We form SSCS by majority vote and DCS by agreement between strands.
set.seed(1)
sim_consensus_error <- function(n_sites = 1e5, fam_size = 6, p = 1e-3){
# top & bottom strand reads
top <- matrix(rbinom(n_sites * fam_size, 1, p), nrow = n_sites)
bot <- matrix(rbinom(n_sites * fam_size, 1, p), nrow = n_sites)
# SSCS: majority vote within each family
sscs_top <- rowMeans(top) > 0.5
sscs_bot <- rowMeans(bot) > 0.5
# DCS: require concordance between SSCS
dcs_call <- sscs_top & sscs_bot
c(
raw_error = mean(top[,1] == 1),
sscs_error_top = mean(sscs_top),
dcs_error = mean(dcs_call)
)
}
# Explore a grid of p and family sizes
grid <- expand.grid(p = c(1e-3, 3e-4, 1e-4), fam = c(3,6,10))
res <- purrr::pmap_dfr(grid, ~{
out <- sim_consensus_error(n_sites = 5e4, fam_size = ..2, p = ..1)
tibble(p = ..1, fam = ..2, raw = out[1], sscs = out[2], dcs = out[3])
})
res %>% mutate(across(c(raw, sscs, dcs), signif, 3)) %>% knitr::kable()#> Warning: There was 1 warning in `mutate()`.
#> ℹ In argument: `across(c(raw, sscs, dcs), signif, 3)`.
#> Caused by warning:
#> ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
#> Supply arguments directly to `.fns` through an anonymous function instead.
#>
#> # Previously
#> across(a:b, mean, na.rm = TRUE)
#>
#> # Now
#> across(a:b, \(x) mean(x, na.rm = TRUE))
| p | fam | raw | sscs | dcs |
|---|---|---|---|---|
| 1e-03 | 3 | 0.00080 | 0 | 0 |
| 3e-04 | 3 | 0.00030 | 0 | 0 |
| 1e-04 | 3 | 0.00012 | 0 | 0 |
| 1e-03 | 6 | 0.00078 | 0 | 0 |
| 3e-04 | 6 | 0.00028 | 0 | 0 |
| 1e-04 | 6 | 0.00004 | 0 | 0 |
| 1e-03 | 10 | 0.00106 | 0 | 0 |
| 3e-04 | 10 | 0.00044 | 0 | 0 |
| 1e-04 | 10 | 0.00010 | 0 | 0 |
Interpretation: SSCS reduces errors roughly with family size; DCS produces ≈p^2 scaling when strand errors are independent. Deviations in real data usually signal correlated/pre-repair artifacts.
tribble(
~Method, ~Core_idea, ~Typical_error_rate, ~Strengths, ~Considerations,
"Standard (CDI)", "Indexed library; no duplex collapsing", "~1e-3 to 1e-4", "Easy, cheap, scalable", "Index hopping; PCR/seq errors dominate",
"DS + UMI (UDI recommended)", "SSCS → DCS across complementary strands", "~1e-7 (empirical)", "Suppresses most PCR/seq errors", "Sensitive to end-repair–copied damage",
"NanoSeq (DS+no end-repair)", "Blunt-ending + ddBTPs arrest; DCS", "≤5e-9 (practical)", "Ultra-rare variant detection; cleaner spectra", "Cost, throughput, chemistry constraints"
) %>% knitr::kable()| Method | Core_idea | Typical_error_rate | Strengths | Considerations |
|---|---|---|---|---|
| Standard (CDI) | Indexed library; no duplex collapsing | ~1e-3 to 1e-4 | Easy, cheap, scalable | Index hopping; PCR/seq errors dominate |
| DS + UMI (UDI recommended) | SSCS → DCS across complementary strands | ~1e-7 (empirical) | Suppresses most PCR/seq errors | Sensitive to end-repair–copied damage |
| NanoSeq (DS+no end-repair) | Blunt-ending + ddBTPs arrest; DCS | ≤5e-9 (practical) | Ultra-rare variant detection; cleaner spectra | Cost, throughput, chemistry constraints |
• Prefer UDI over CDI for high-plex panels.
• Track UMI families and require minimum family sizes for SSCS.
• Enforce duplex pairing heuristics (complementary SSCS only; orientation/insert compatibility).
• Guard against chimeras/concatemers and soft-clipped alignments near ligation sites.
• Use context-specific filters (e.g., strand concordance, read position bias, base quality, map quality, homopolymer runs).