Pieter Moris
05/20/2026, 1:20 PMbwa , deacon and picard are? Or is bored and interested in a puzzle?
1. I just updated a custom deacon module in my pipeline to the latest release in nf-core modules. The underlying container is the same, but there are some slight differences in prefix checks, a tab got changed into four spaces, etc. The main command itself is unchanged.
2. When I run the pipeline on my test data, the fastq files filtered by deacon files no longer match with the previous ones. I verified with seqkit sum that the actual reads are the same, so it must just be the order in which they appear that differs. I saw some speed differences in the deacon logs too.
3. The bam files produced by bwa also differ, but gatk CompareSAMs and samtools flagstat do not entirely agree on how they differ, and the latter differences disappear when sorting the file (even though it should already have been sorted in my pipeline now that I think about it). Flagstat sees the largest amount of difference after the bam files have been merged per-lane by picard.
EDIT: moving logs to thread to save space.Pieter Moris
05/20/2026, 1:21 PM# comparison of initial bam files
āāā¶ gatk CompareSAMs ./results-rebase-deacon-update-stash-revert/alignment/bwa/bam_initial/106264-002-079-LANE_L007-FC_22NY35LT3.bam ./results-rebase-deacon-update-stash/alignment/bwa/bam_initial/106264-002-079-LANE_L007-FC_22NY35LT3.bam -O comparison.tsv
# tsv output:
MAPPINGS_MATCH MAPPINGS_DIFFER UNMAPPED_BOTH UNMAPPED_LEFT UNMAPPED_RIGHT MISSING_LEFT MISSING_RIGHT DUPLICATE_MARKINGS_DIFFER ARE_EQUAL
13604 18 58 0 0 0 0 0 N
# re-running CompareSAMs with --LENIENT_LOW_MQ_ALIGNMENT shows the same results
Flagstat output differs between the deduplicated and sorted
# initial bam files produced from the deacon-filtered fastq files
āāā¶ colordiff <(samtools sort ./results-rebase-deacon-update-stash-revert/alignment/bwa/bam_initial/106264-002-079-LANE_L007-FC_22NY35LT3.bam | samtools flagstat) <(samtools sort ./results-rebase-deacon-update-stash/alignment/bwa/bam_initial/106264-002-079-LANE_L007-FC_22NY35LT3.bam | samtools flagstat)
āāā¶
āāā¶ colordiff <(samtools sort ./results-rebase-deacon-update-stash-revert/alignment/bwa/bam_initial/106264-002-079-LANE_L008-FC_22LJC2LT4.bam | samtools flagstat) <(samtools sort ./results-rebase-deacon-update-stash/alignment/bwa/bam_initial/106264-002-079-LANE_L008-FC_22LJC2LT4.bam | samtools flagstat)
āāā¶
# folder where flagstat is stored after sort and markdup step in pipeline (and merging of lanes)
āāā¶ colordiff ./results-rebase-deacon-update-stash-revert/alignment/samtools/stats/106264-002-079.flagstat ./results-rebase-deacon-update-stash/alignment/samtools/stats/106264-002-079.flagstat
1c1
< 29523 + 0 in total (QC-passed reads + QC-failed reads)
---
> 29524 + 0 in total (QC-passed reads + QC-failed reads)
4,7c4,7
< 521 + 0 supplementary
< 3947 + 0 duplicates
< 3947 + 0 primary duplicates
< 29403 + 0 mapped (99.59% : N/A)
---
> 522 + 0 supplementary
> 3951 + 0 duplicates
> 3951 + 0 primary duplicates
> 29404 + 0 mapped (99.59% : N/A)
15,16c15,16
< 272 + 0 with mate mapped to a different chr
< 230 + 0 with mate mapped to a different chr (mapQ>=5)
---
> 274 + 0 with mate mapped to a different chr
> 231 + 0 with mate mapped to a different chr (mapQ>=5)
Oddly enough, when sorting the files again manually, the differences disappear:
āāā¶ colordiff <(samtools sort ./results-rebase-deacon-update-stash-revert/alignment/bwa/bam_sort_markdup/106264-002-079.sort.markdup.bam | samtools flagstat) <(samtools sort ./results-rebase-deacon-update-stash/alignment/bwa/bam_sort_markdup/106264-002-079.sort.markdup.bam | samtools flagstat)
āāā¶
Running CompareSAMs on the dedup.sorted bams shows that the separate lane fastq files were fed to picard in a different order between the old and the new run, and a (lenient) comparison shows slight differences once again (more than between the initial bam files, and more than reported by flagstat as well):
āāā¶ gatk CompareSAMs --LENIENT_LOW_MQ_ALIGNMENT ./results-rebase-deacon-update-stash-revert/alignment/bwa/bam_sort_markdup/106264-002-079.sort.markdup.bam ./results-rebase-deacon-update-stash/alignment/bwa/bam_sort_markdup/106264-002-079.sort.markdup.bam -O comparison-dedupsort.tsv
MAPPINGS_MATCH MAPPINGS_DIFFER UNMAPPED_BOTH UNMAPPED_LEFT UNMAPPED_RIGHT MISSING_LEFT MISSING_RIGHT DUPLICATE_MARKINGS_DIFFER ARE_EQUAL
28881 1 120 0 0 0 0 4 N
My current hunch is that either
1. slight changes to the module/workflow causes
or
2. the deacon steps always produce slightly random results (in terms of read ordering) and I hadn't noticed due to always caching results from my workdir
lead to a different order of process execution, causing laned-fastq files to be fed to picard in a different order, and it is picard that is sensitive to order to some degree. Although that doesn't explain the differences between the per-lane files spotted by CompareSAMs.Alec Wysoker
05/20/2026, 1:45 PM// Collect a channel of tuple(meta, value) in which meta.collectIndex is an integer on which to sort.
// Return the collected values in order by collectIndex. Return value is a channel containing a single list item.
def collectInOrder(inChannel) {
def ret = inChannel
.map { meta, value ->
tuple(meta.collectIndex, value)
}
.toSortedList { a, b -> a[0] <=> b[0] }
.map { sorted ->
sorted.collect { pair ->
pair[1]
}
}
return ret
}
Honestly, my old brain glazes over when I look at the above code, but it appears to do the right thing.Thomas Danhorn
05/21/2026, 8:51 AMdeacon, but if you run it multi-threaded and the output is not explicitly sorted, the relative speed of the threads would determine the order of the reads in the output.Pieter Moris
05/22/2026, 8:56 AMbwa step.
@Thomas Danhorn: ooh, that sounds plausible!
---
I've done some more sleuthing and believe that the differences I'm seeing are due to a documented behaviour of bwa.
1. Deacon's output is deterministic in terms of the number of filtered reads, but their order in the fastq files is random (due to multi-threading). Of course, R1 / R2 reads are kept in the same order in their respective files.
2. bwa can produce slight differences in output depending on the order of the reads. See https://github.com/malariagen/pipelines/issues/41, https://www.biostars.org/p/238628/, https://github.com/CCDG/Pipeline-Standardization/issues/2 and https://github.com/nf-core/sarek/issues/101. Supposedly the hidden option -K 1000000 (which I'm using, just like #CGFUX04HZ), should make bwa more deterministic, but apparently it's not enough in this case.
3. The downstream outputs of e.g. picard consequently differ. Additionally, even if the bam outputs were identical, the random order of per-lane bam files in the nextflow channel could lead to different combined bam files (same content, but different order and header). Lastly, I've made sure that the bam files are coordinate-sorted in my pipeline before feeding them to picard.
I've added the output of some smaller test files below:
āāā¶ zcat hostremoval/deacon/filter/fastq/106264-002-079-LANE_L008-FC_22LJC2LT4_1.fq.gz | head -n 10000 > sample_L8_R1.fq.gz
āāā¶ zcat hostremoval/deacon/filter/fastq/106264-002-079-LANE_L008-FC_22LJC2LT4_2.fq.gz | head -n 10000 > sample_L8_R2.fq.gz
āāā¶ for i in *.fq.gz; do seqkit sort ${i} > "$(basename ${i} .fq.gz)".sort.fq.gz; done
āāā¶ bwa mem -Y -K 100000000 \
-R '@RG\tID:106264-002-079.22NY35LT3.L007\tSM:106264-002-079\tLB:106264-002-079\tPL:ILLUMINA\tPU:22NY35LT3.L007' \
PlasmoDB-68_Pfalciparum3D7_Genome \
sample_L8_R1.fq.gz sample_L8_R2.fq.gz \
> sample_L8.K.bam
āāā¶ bwa mem -Y \
-R '@RG\tID:106264-002-079.22NY35LT3.L007\tSM:106264-002-079\tLB:106264-002-079\tPL:ILLUMINA\tPU:22NY35LT3.L007' \
PlasmoDB-68_Pfalciparum3D7_Genome \
sample_L8_R1.fq.gz sample_L8_R2.fq.gz \
> sample_L8.bam
āāā¶ bwa mem -Y -K 100000000 \
-R '@RG\tID:106264-002-079.22NY35LT3.L007\tSM:106264-002-079\tLB:106264-002-079\tPL:ILLUMINA\tPU:22NY35LT3.L007' \
PlasmoDB-68_Pfalciparum3D7_Genome \
sample_L8_R1.sort.fq.gz sample_L8_R2.sort.fq.gz \
> sample_L8.sort.K.bam
āāā¶ bwa mem -Y \
-R '@RG\tID:106264-002-079.22NY35LT3.L007\tSM:106264-002-079\tLB:106264-002-079\tPL:ILLUMINA\tPU:22NY35LT3.L007' \
PlasmoDB-68_Pfalciparum3D7_Genome \
sample_L8_R1.sort.fq.gz sample_L8_R2.sort.fq.gz \
> sample_L8.sort.bam
āāā¶ for i in *.bam; do printf "\n$i\n"; samtools flagstat $i; done
sample_L8.K.bam
5090 + 0 in total (QC-passed reads + QC-failed reads)
5000 + 0 primary
0 + 0 secondary
90 + 0 supplementary
0 + 0 duplicates
0 + 0 primary duplicates
5068 + 0 mapped (99.57% : N/A)
4978 + 0 primary mapped (99.56% : N/A)
5000 + 0 paired in sequencing
2500 + 0 read1
2500 + 0 read2
4762 + 0 properly paired (95.24% : N/A)
4974 + 0 with itself and mate mapped
4 + 0 singletons (0.08% : N/A)
52 + 0 with mate mapped to a different chr
45 + 0 with mate mapped to a different chr (mapQ>=5)
sample_L8.bam
5090 + 0 in total (QC-passed reads + QC-failed reads)
5000 + 0 primary
0 + 0 secondary
90 + 0 supplementary
0 + 0 duplicates
0 + 0 primary duplicates
5068 + 0 mapped (99.57% : N/A)
4978 + 0 primary mapped (99.56% : N/A)
5000 + 0 paired in sequencing
2500 + 0 read1
2500 + 0 read2
4762 + 0 properly paired (95.24% : N/A)
4974 + 0 with itself and mate mapped
4 + 0 singletons (0.08% : N/A)
52 + 0 with mate mapped to a different chr
45 + 0 with mate mapped to a different chr (mapQ>=5)
sample_L8.sort.K.bam
5090 + 0 in total (QC-passed reads + QC-failed reads)
5000 + 0 primary
0 + 0 secondary
90 + 0 supplementary
0 + 0 duplicates
0 + 0 primary duplicates
5068 + 0 mapped (99.57% : N/A)
4978 + 0 primary mapped (99.56% : N/A)
5000 + 0 paired in sequencing
2500 + 0 read1
2500 + 0 read2
4762 + 0 properly paired (95.24% : N/A)
4974 + 0 with itself and mate mapped
4 + 0 singletons (0.08% : N/A)
50 + 0 with mate mapped to a different chr
44 + 0 with mate mapped to a different chr (mapQ>=5)
sample_L8.sort.bam
5090 + 0 in total (QC-passed reads + QC-failed reads)
5000 + 0 primary
0 + 0 secondary
90 + 0 supplementary
0 + 0 duplicates
0 + 0 primary duplicates
5068 + 0 mapped (99.57% : N/A)
4978 + 0 primary mapped (99.56% : N/A)
5000 + 0 paired in sequencing
2500 + 0 read1
2500 + 0 read2
4762 + 0 properly paired (95.24% : N/A)
4974 + 0 with itself and mate mapped
4 + 0 singletons (0.08% : N/A)
50 + 0 with mate mapped to a different chr
44 + 0 with mate mapped to a different chr (mapQ>=5)
The numbers for "mate mapped to a different chr" are ever so slightly off depending on the order of the reads in the fastq files, regardless of the -K option.