Repository: Sydney-Informatics-Hub/ONT-bacpac-nf
Pull request #76: Add autocycler
Author: login: mgeaghan
Created: 2025-05-14T06:58:32Z | Merged: 2025-05-23T06:40:31Z

This PR addresses issue #69 - adding autocycler to the workflow.

The trycycler stage is still present but has been moved to a subworkflow. Autocycler has also been implemented as a subworkflow. The pipeline can be run with either method - autocycler is the default.

Two new parameters have been added to the workflow:

- `consensus_method`: can be either `autocycler` or `trycycler`; determines which method to use; default is `autocycler`
- `subsamples`: determines the number of subsamples to use when running either trycycler or autocycler.
    - trycycler can be run without subsampling by setting `--subsamples 1`
    - autocycler *must* be run with subsampling; setting `--subsamples` to less than 2 will cause an error


Diff:
diff --git a/README.md b/README.md
index 0b1a554..c2b633a 100755
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ A rapid and portable workflow for pond-side sequencing of bacterial pathogens fo
 ## User guide 
 
 See the following docs: 
+- Getting started: [docs/getting-started.md](docs/getting-started.md)
 - Gadi execution: [docs/gadi-execution.md](docs/gadi-execution.md)
 - Interpreting results: [docs/interpreting-results.md](docs/interpreting-results.md)
 - Developer instructions: [docs/developers.md](docs/developers.md)
diff --git a/bin/classify_trycycler_clusters.py b/bin/classify_trycycler_clusters.py
index 2f41fe2..c08df7c 100755
--- a/bin/classify_trycycler_clusters.py
+++ b/bin/classify_trycycler_clusters.py
@@ -27,7 +27,8 @@
 
 # Retrieve command-line arguments
 sample_id = sys.argv[1]
-number_of_assemblers = 2
+number_of_assemblers = int(sys.argv[2])
+assert number_of_assemblers > 0, 'Error: Number of assemblers must be at least 1'
 
 
 def get_contigs_clusters(data_clusters,number_of_assemblies):
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..16e73fe
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,152 @@
+# Getting started
+
+The bacpac pipeline is designed as a rapid and portable workflow to process ONT long-read sequencing data for the purpose of pond-side detection of bacterial pathogens for sustainable aquaculture.
+
+The pipeline is designed using Nextflow and utilises common, well-tested tools for bacterial genome and plasmid assembly, quality control, DNA contamination detection, and antibiotic resistance gene annotation.
+
+## Software requirements
+
+At a minimum, you will require the following installed on your system:
+
+- Java version 17 or later
+- Nextflow version 24 or later
+    - See the [Nextflow docs](https://www.nextflow.io/docs/latest/install.html) for information on installing both Nextflow and Java
+- Singularity
+
+The workflow has been designed to primarily run on NCI's [gadi](https://nci.org.au/our-systems/hpc-systems) high performance computing infrastructure.
+
+## Pre-downloading the Kraken2 database
+
+We recommend pre-downloading a Kraken2 database as it can be quite slow to download as part of the pipeline. You can find links to these databases [here](https://benlangmead.github.io/aws-indexes/k2). We have tested this pipeline with the [Standard Kraken2 database from 12 January 2024](https://genome-idx.s3.amazonaws.com/kraken/k2_standard_20240112.tar.gz).
+
+## Sequencing summary
+
+ONT sequencing runs will generate a sequencing summary text file, named something like `sequencing_summary_*.txt`. This file is used by multiple quality control steps of the pipeline and must be supplied with the `--sequencing_summary` parameter.
+
+## Running the workflow
+
+The pipeline takes either a sequencing data directory or a CSV samplesheet as its input.
+
+A sequencing data directory should contain one `.zip` file per sample, each containing all FASTQs associated with that sample. It can be specified on the command line as follows:
+
+```bash
+nextflow run main.nf --input_directory /path/to/dataset [...]
+```
+
+If a samplesheet is provided, it should be formatted as follows:
+
+```csv
+barcode,batch,file_path
+barcode01,batch1,/path/to/dataset/barcode01.zip
+barcode02,batch1,/path/to/dataset/barcode02.zip
+barcode03,batch2,/path/to/dataset/barcode03.zip
+barcode04,batch2,/path/to/dataset/barcode04.zip
+```
+
+This can be specified with:
+
+```bash
+nextflow run main.nf --samplesheet /path/to/samplesheet.csv [...]
+```
+
+The sequencing summary file should be supplied with:
+
+```bash
+nextflow run main.nf --sequencing_summary /path/to/sequencing_summary.txt [...]
+```
+
+If supplying a pre-downloaded Kraken2 database, use the `--kraken2_db` parameter to provide the directory containing the database files (i.e. `hash.k2d`, `ktaxonomy.tsv`, `opts.k2d`, and `taxo.k2d`):
+
+```bash
+nextflow run main.nf --kraken2_db /path/to/kraken2/db [...]
+```
+
+### Execution on gadi
+
+If running on NCI's gadi, you will also need to provide the following paramters:
+
+- `--gadi_account <PROJ>`
+    - `PROJ` should be your NCI project ID, e.g. `ab01`
+- `--gadi_storage <STORAGE_STRING>`
+    - `STORAGE_STRING` should be a `+`-delimited list of NCI storage systems that you will require, e.g. `scratch/ab01` or `scratch/ab01+gdata/ab01`
+
+You will also need to specify to use the `gadi` Nextflow profile, e.g.:
+
+```bash
+nextflow run main.nf -profile gadi [...]
+```
+For detailed instructions on scaling up the pipeline on Gadi, see [docs/gadi-execution.md](docs/gadi-execution.md).
+
+### Accuracy mode
+
+The pipeline expects by default that your samples have been called with fast basecalling - a quick but low-accuracy mode provided by Nanopore. If you have run basecalling with higher accuracy, you should also enable the `high_accuracy` profile to ensure the genome assembly steps have enough resources to complete successfully:
+
+```bash
+nextflow run main.nf -profile gadi,high_accuracy [...]
+```
+
+### Consensus assembly method
+
+Previous versions of this pipeline used an automated [Trycycler](https://github.com/rrwick/Trycycler) workflow to take the assemblies generated by Flye and Unicycler and create a consensus sequence.
+
+Since then, a newer, fully-automated tool has been developed by the same author, [Autocycler](https://github.com/rrwick/Autocycler). Our tests have shown this to be superior to the Trycycler method and we strongly recommend using Autocycler. As such, this is now the default consensus assembly method used in this pipeline.
+
+If you wish, you can still use the Trycycler method by supplying the `--consensus_method trycycler` parameter:
+
+```bash
+nextflow run main.nf --consensus_method trycycler [...]
+```
+
+#### Subsampling
+
+Both Trycycler and Autocycler support subsampling your reads prior to assembly and consensus generation. This can improve the clustering step and improve the accuracy of your consensus sequence. By default, the pipeline is set to use 4 subsamples with both Trycycler and Autocycler. You can change this value with the `--subsamples` parameter:
+
+```bash
+# Trycycler
+nextflow run main.nf --consensus_method trycycler --subsamples 8 [...]
+
+# Autocycler
+nextflow run main.nf --subsamples 8 [...]
+```
+
+For Trycycler, you can disable subsampling altogether by using `--subsamples 1`. Subsampling is required for the Autocycler implementation and cannot be turned off.
+
+### Putting it together
+
+A typical run might look like the following. Assuming a run on gadi, with a pre-downloaded Kraken2 database at `/scratch/ab01/kraken_db`, a samplesheet at `/scratch/ab01/samplesheet.csv`, a sequencing summary file at `/scratch/ab01/dataset/sequencing_summary.txt`, and high-accuracy basecalling, we would do the following:
+
+```bash
+module load nextflow/24.04.1
+module load singularity
+
+nextflow run main.nf \
+    --samplesheet /scratch/ab01/samplesheet.csv \
+    --kraken2_db /scratch/ab01/kraken_db \
+    --sequencing_summary /scratch/ab01/dataset/sequencing_summary.txt \
+    --gadi_account ab01 \
+    --gadi_storage scratch/ab01 \
+    -profile gadi,high_accuracy \
+    -resume
+```
+
+## Outputs
+
+By default, the pipeline will output all results to the `results/` directory. This can be changed by supplying the `--outdir` parameter, e.g.:
+
+```bash
+nextflow run main.nf --outdir 2025-06-01_results [...]
+```
+
+The output directory will contain the following sub-directories:
+
+- `assemblies`: Your completed genome assemblies.
+- `quality_control`: Quality information assessed by QUAST, BUSCO, Kraken2, PycoQC, and NanoPlot.
+- `annotations`: Annotations for antimicrobial resistance genes, virulence genes, plasmid annotations, and bacterial genome annotations.
+- `taxonomy`: Phylogenetic tree information.
+- `report`: A final MultiQC report summarising the pipeline run.
+
+For detailed information about interpreting the pipeline results, see [docs/interpreting-results.md](docs/interpreting-results.md)
+
+## Next steps
+
+This pipeline has been primarily developed for use on NCI's gadi. For more information about running the pipeline at scale on that system, see [docs/gadi-execution.md](docs/gadi-execution.md).
\ No newline at end of file
diff --git a/docs/interpreting-results.md b/docs/interpreting-results.md
index 8bc8862..8305cc1 100644
--- a/docs/interpreting-results.md
+++ b/docs/interpreting-results.md
@@ -70,7 +70,9 @@ See the [QUAST docs](https://quast.sourceforge.net/docs/manual.html#sec3) for de
 
 #### Chromosome assemblies
 
-This pipeline has implemented a hybrid assembly strategy that first generates assemblies using Flye and Unicycler, then attempts to generate a consensus assembly using both Flye and Unicycler assemblies. Generating a consensus assembly using Trycycler typically requires manual interventions, however we have automated this process using sensible default parameters. See the Trycycler [publication](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-021-02483-z) and [code base](https://github.com/rrwick/Trycycler) for  information on how Trycycler generates a consensus.
+This pipeline has implemented a hybrid assembly strategy that first generates assemblies using Flye and Unicycler, then attempts to generate a consensus assembly using both Flye and Unicycler assemblies. This consensus assembly is generated by default using [Autocycler](https://github.com/rrwick/Autocycler). A previous version of this pipeline used [Trycycler](https://github.com/rrwick/Trycycler), which is still available to use if you choose. However, generating a consensus assembly using Trycycler typically requires manual interventions; our implementation automates the process using sensible default parameters. From our testing, we have concluded that Autocycler is now the superior choice and **we strongly recommend using the Autocycler consensus method**. For more information on these tools, see the Trycycler [publication](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-021-02483-z) and [code base](https://github.com/rrwick/Trycycler), and the Autocycler [publication](https://doi.org/10.1101/2025.05.12.653612) and [code base](https://github.com/rrwick/Autocycler) for information on how they generate consensus sequences.
+
+If you decide you want to use Trycycler, simply run the Nextflow pipeline with the additional parameter: `--consensus_method trycycler`.
 
 Where a high quality consensus assembly cannot be generated, the pipeline will output the Flye assembly instead. Our benchmarking has shown that Flye generates more contiguous chromosomal assemblies than Unicycler. Regardless of the assembly method used, the pipeline will output polished assemblies into the `${params.outdir}/assemblies` directory. Polished assemblies that passed consensus approach will be saved to `${barcode}_consensus/` and assemblies that failed the consensus approach will be saved to `${barcode}_flye/`. 
 
diff --git a/main.nf b/main.nf
index 0dcce8b..52867d4 100755
--- a/main.nf
+++ b/main.nf
@@ -20,15 +20,10 @@ include { get_bakta } from './modules/get_bakta'
 include { kraken2 } from './modules/run_kraken2'
 include { flye_assembly } from './modules/run_flye'
 include { unicycler_assembly } from './modules/run_unicycler'
-include { trycycler_cluster } from './modules/run_trycycler_cluster'
-include { classify_trycycler } from './modules/run_trycycler_classify'
-include { trycycler_reconcile } from './modules/run_trycycler_reconcile'
+include { trycycler } from './subworkflows/trycycler'
+include { autocycler } from './subworkflows/autocycler'
 include { select_assembly } from './modules/select_assembly'
-include { trycycler_msa } from './modules/run_trycycler_msa'
-include { trycycler_partition } from './modules/run_trycycler_partition'
-include { trycycler_consensus} from './modules/run_trycycler_consensus'
 include { medaka_polish_denovo } from './modules/run_medaka_polish_denovo'
-include { medaka_polish_consensus } from './modules/run_medaka_polish_consensus'
 include { plassembler } from './modules/run_plassembler'
 include { bakta_annotation_plasmids } from './modules/run_bakta_annotation_plasmids'
 include { busco_annotation_plasmids } from './modules/run_busco_annotation_plasmids'
@@ -242,106 +237,26 @@ if ( params.help || (!params.input_directory && !params.samplesheet) || !params.
     }
     .collect()
 
-  // RUN TRYCYCLER (CONSENSUS ASSEMBLY) IF SUFFICIENT # CONTIGS
-  // TRYCYCLER: Cluster contigs
-  trycycler_cluster(denovo_assembly_contigs.run_trycycler)
-
-  barcode_cluster_sizes =
-    // trycycler_cluster filters out small contigs (default < 5000nt) and can
-    // result in < 2 contigs here again. Use the same branching logic to avoid
-    // < 2 contig errors.
-    trycycler_cluster.out.phylip
-    .map { barcode, contigs_phylip ->
-        def phylip_lines = contigs_phylip.text.readLines().size - 1 // exclude header
-        return [barcode, phylip_lines]
-    }
-    .branch { barcode, phylip_lines ->
-        run_trycycler: phylip_lines >= 3
-        skip_trycycler: phylip_lines < 3
-        // create a channel from skip_trycycler if failed barcodes need to be reported/used
-    }
-
-  // TRYCYCLER: Classify clusters
-  clusters_to_classify =
-    // Add path to clusters with sufficient contigs
-    trycycler_cluster.out.clusters
-    .join(barcode_cluster_sizes.run_trycycler)
-
-  classify_trycycler(clusters_to_classify)
-
-  // TRYCYCLER: Reconcile contigs
-  clusters_to_reconcile_flat = 
-    classify_trycycler.out.clusters_to_reconcile
-    .join(porechop.out.trimmed_fq, by: 0)
-    // from [barcode, [pathA, pathB], ...]
-    // to [barcode, pathA, ...]; [barcode, pathB, ...]
-    .transpose()
-
-  trycycler_reconcile(clusters_to_reconcile_flat)
-
-  reconciled_cluster_dirs = 
-    trycycler_reconcile.out.reconciled_seqs // successfully reconciled
-    .map { barcode, seq ->
-        // drops the last part of the path (reconciled_seqs file) as trycycler
-        // searches the parent dir for it
-        Path cluster_dir = seq.getParent()
-        return [barcode, cluster_dir] 
-    }
-  
-  // TRYCYCLER: Align
-  trycycler_msa(reconciled_cluster_dirs)
-
-  // TRYCYCLER: Partitioning reads
-  clusters_to_partition =
-    trycycler_msa.out.results_dir
-    .groupTuple()
-    .join(porechop.out.trimmed_fq)
-
-  trycycler_partition(clusters_to_partition)
-
-  // TRYCYCLER: Generate consensus assemblies  
-  clusters_for_consensus =
-    trycycler_partition.out.partitioned_reads
-    .transpose() // "ungroup" tuple
-    .map { barcode, reads ->
-        // Get directories of successfully partitioned reads
-        Path cluster_dir = reads.getParent()
-        return [barcode, cluster_dir]
-    }
+  if (params.consensus_method == 'trycycler') {
+    // RUN TRYCYCLER (CONSENSUS ASSEMBLY) IF SUFFICIENT # CONTIGS
+    trycycler(porechop.out.trimmed_fq, denovo_assembly_contigs.run_trycycler)
 
-  trycycler_consensus(clusters_for_consensus)
+    polished_consensus_per_barcode = trycycler.out.polished_consensus_per_barcode
+  } else if (params.consensus_method == 'autocycler') {
+    // RUN AUTOCYCLER
 
-  // MEDAKA: Polish consensus assembly
-  consensus_dir = 
-    // Get parent dir for assembly
-    trycycler_consensus.out.cluster_assembly
-    .map { barcode, assembly ->
-        Path assembly_dir = assembly.getParent()
-        return [barcode, assembly_dir]
+    // First, check that params.subsamples > 1
+    if (!(params.subsamples.toInteger() > 1)) {
+      log.info "Error: autocycler must be run with > 1 subsamples"
+      System.exit(1)
     }
+    
+    autocycler(porechop.out.trimmed_fq)
 
-  medaka_polish_consensus(consensus_dir)
-
-  // CAT: Combine polished clusters consensus assemblies into a single fasta
-  polished_clusters = 
-    medaka_polish_consensus.out.cluster_assembly
-    .groupTuple()
-    .map { barcode, fastas ->
-        // Need to avoid filename collisions with consensus.fasta files
-        // by renaming them
-        def indexed_fas = 
-            fastas.withIndex()
-            .collect { fa, idx ->
-                def new_fa = fa.getParent() / "consensus_${idx}.fasta"
-                fa.copyTo(new_fa)
-                return new_fa
-            }
-        return [barcode, indexed_fas]
-    }
-  
-  concat_fastas(polished_clusters)
-
-  polished_consensus_per_barcode = concat_fastas.out
+    polished_consensus_per_barcode = autocycler.out.polished_consensus_per_barcode
+  } else {
+    error 'Invalid value for `consensus_method`: ' + params.consensus_method
+  }
 
   // MEDAKA: POLISH DE NOVO ASSEMBLIES
   unpolished_denovo_assemblies =
@@ -359,14 +274,7 @@ if ( params.help || (!params.input_directory && !params.samplesheet) || !params.
   medaka_polish_denovo(unpolished_denovo_assemblies)
 
   // ASSEMBLY QC
-  all_polished =
-    polished_consensus_per_barcode
-    .map { barcode, consensus_fa ->
-        // technically should be "trycycler" but want to separate it out from
-        // the denovo assemblies clearly
-        String assembler = "consensus"
-        return [barcode, assembler, consensus_fa]
-    }
+  all_polished = polished_consensus_per_barcode
     .mix(medaka_polish_denovo.out.assembly)
 
   // TODO: probably better to collect all per-barcode assemblies in one quast
diff --git a/modules/run_autocycler_cluster.nf b/modules/run_autocycler_cluster.nf
new file mode 100755
index 0000000..d885c57
--- /dev/null
+++ b/modules/run_autocycler_cluster.nf
@@ -0,0 +1,19 @@
+process autocycler_cluster {
+  tag "AUTOCYCLER CLUSTER: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+
+  input:
+  tuple val(barcode), path(autocycler_dir)
+
+  output:
+  tuple val(barcode), path("autocycler_cluster_out"), emit: cluster_out
+  tuple val(barcode), path("autocycler_cluster_out/clustering/qc_pass/cluster_*"), emit: pass_clusters
+
+  script:
+  """
+  cp -rL ${autocycler_dir} autocycler_cluster_out 
+
+  autocycler cluster \\
+    --autocycler_dir autocycler_cluster_out
+  """
+}
diff --git a/modules/run_autocycler_combine.nf b/modules/run_autocycler_combine.nf
new file mode 100755
index 0000000..c0b2d90
--- /dev/null
+++ b/modules/run_autocycler_combine.nf
@@ -0,0 +1,25 @@
+process autocycler_combine {
+  tag "AUTOCYCLER COMBINE: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+  publishDir "${params.outdir}/assemblies/${barcode}_consensus", mode: 'copy'
+
+  input:
+  tuple val(barcode), path(autocycler_cluster_dir, stageAs: "autocycler_cluster_dir"), path(pass_cluster_dirs)
+
+  output:
+  tuple val(barcode), path("autocycler_out"), emit: autocycler_out
+
+  script:
+  """
+  cp -rL autocycler_cluster_dir autocycler_out
+
+  # Remove the passing cluster directories from the autocycler cluster directory
+  # and replace with the pass_cluster_dirs from the trim + resolve stages
+  rm -r autocycler_out/clustering/qc_pass/*
+  cp -rL ${pass_cluster_dirs} autocycler_out/clustering/qc_pass/
+
+  autocycler combine \\
+    --autocycler_dir autocycler_out \\
+    --in_gfas autocycler_out/clustering/qc_pass/cluster_*/5_final.gfa
+  """
+}
diff --git a/modules/run_autocycler_compress.nf b/modules/run_autocycler_compress.nf
new file mode 100755
index 0000000..3d249d5
--- /dev/null
+++ b/modules/run_autocycler_compress.nf
@@ -0,0 +1,24 @@
+process autocycler_compress {
+  tag "AUTOCYCLER COMPRESS: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+
+  input:
+  tuple val(barcode), path(assembly_fastas)
+
+  output:
+  tuple val(barcode), path("autocycler_compress_out"), emit: compressed
+
+  script:
+  """
+  mkdir assemblies
+  for d in ${assembly_fastas}
+  do
+    cp \$d/assembly.fasta assemblies/\$(basename \$d).fasta
+  done
+
+  autocycler compress \\
+    --assemblies_dir assemblies \\
+    --autocycler_dir autocycler_compress_out \\
+    --threads ${task.cpus}
+  """
+}
diff --git a/modules/run_autocycler_resolve.nf b/modules/run_autocycler_resolve.nf
new file mode 100755
index 0000000..7d34e8b
--- /dev/null
+++ b/modules/run_autocycler_resolve.nf
@@ -0,0 +1,18 @@
+process autocycler_resolve {
+  tag "AUTOCYCLER RESOLVE: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+
+  input:
+  tuple val(barcode), val(cluster_id), path(cluster_dir, stageAs: "cluster_dir")
+
+  output:
+  tuple val(barcode), val(cluster_id), path("${cluster_id}"), emit: resolved_cluster
+
+  script:
+  """
+  cp -rL cluster_dir ${cluster_id}
+
+  autocycler resolve \\
+    --cluster_dir ${cluster_id}
+  """
+}
diff --git a/modules/run_autocycler_subsample.nf b/modules/run_autocycler_subsample.nf
new file mode 100755
index 0000000..8d73027
--- /dev/null
+++ b/modules/run_autocycler_subsample.nf
@@ -0,0 +1,20 @@
+process autocycler_subsample {
+  tag "SUBSAMPLING FASTQS: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+
+  input:
+  tuple val(barcode), path(trimmed_fq), path(genome_size_txt)
+
+  output:
+  tuple val(barcode), path("${barcode}_subsets/*.fastq"), emit: subsets
+
+  script:
+  n_subsamples = params.subsamples.toInteger()
+  """
+  autocycler subsample \\
+    --reads $trimmed_fq \\
+    --out_dir ${barcode}_subsets \\
+    --count ${n_subsamples} \\
+    --genome_size \$(cat ${genome_size_txt})
+  """
+}
diff --git a/modules/run_autocycler_table.nf b/modules/run_autocycler_table.nf
new file mode 100755
index 0000000..0426f20
--- /dev/null
+++ b/modules/run_autocycler_table.nf
@@ -0,0 +1,20 @@
+process autocycler_table {
+  tag "AUTOCYCLER TABLE: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+  publishDir "${params.outdir}/assemblies/${barcode}_consensus", mode: 'copy'
+
+  input:
+  tuple val(barcode), path(autocycler_dir)
+
+  output:
+  tuple val(barcode), path("metrics.tsv"), emit: metrics
+
+  script:
+  """
+  autocycler table > metrics.tsv  # Header row
+  autocycler table \\
+    --autocycler_dir autocycler_out \\
+    --name ${barcode} \\
+    >> metrics.tsv
+  """
+}
diff --git a/modules/run_autocycler_trim.nf b/modules/run_autocycler_trim.nf
new file mode 100755
index 0000000..c44cf3c
--- /dev/null
+++ b/modules/run_autocycler_trim.nf
@@ -0,0 +1,19 @@
+process autocycler_trim {
+  tag "AUTOCYCLER TRIM: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+
+  input:
+  tuple val(barcode), val(cluster_id), path(cluster_dir, stageAs: "cluster_dir")
+
+  output:
+  tuple val(barcode), val(cluster_id), path("${cluster_id}"), emit: trimmed_cluster
+
+  script:
+  """
+  cp -rL cluster_dir ${cluster_id}
+
+  autocycler trim \\
+    --cluster_dir ${cluster_id} \\
+    --threads ${task.cpus}
+  """
+}
diff --git a/modules/run_flye_subset.nf b/modules/run_flye_subset.nf
new file mode 100755
index 0000000..c17a10b
--- /dev/null
+++ b/modules/run_flye_subset.nf
@@ -0,0 +1,20 @@
+process flye_assembly_subset {
+  tag "ASSEMBLING SUBSET GENOME WITH FLYE: ${barcode}"
+  container 'quay.io/biocontainers/flye:2.9.3--py310h2b6aa90_0'
+
+  input:
+  tuple val(barcode), path(subset_fq)
+
+  output:
+  tuple val(barcode), path("*"), emit: flye_assembly
+
+  script: 
+  """
+  SUBSAMPLEIDX=\$(basename ${subset_fq} .fastq | cut -d '_' -f 2)
+  OUTDIR="${barcode}_\${SUBSAMPLEIDX}_flye_assembly"
+  flye \\
+    --nano-hq ${subset_fq} \\
+    --threads ${task.cpus} \\
+    --out-dir \${OUTDIR}
+  """
+}
diff --git a/modules/run_trycycler_classify.nf b/modules/run_trycycler_classify.nf
index 2214aff..01a39c8 100755
--- a/modules/run_trycycler_classify.nf
+++ b/modules/run_trycycler_classify.nf
@@ -9,8 +9,9 @@ process classify_trycycler {
   tuple val(barcode), path("${barcode}_discarded/*"), emit: clusters_to_discard, optional: true
   tuple val(barcode), path("${barcode}_for_reconciliation/*"), emit: clusters_to_reconcile
 
-  script: 
+  script:
+  n_assemblers = 2 * params.subsamples.toInteger()
   """
-  classify_trycycler_clusters.py ${barcode} 
+  classify_trycycler_clusters.py ${barcode} ${n_assemblers}
   """
 }
diff --git a/modules/run_trycycler_cluster_subset.nf b/modules/run_trycycler_cluster_subset.nf
new file mode 100755
index 0000000..278206a
--- /dev/null
+++ b/modules/run_trycycler_cluster_subset.nf
@@ -0,0 +1,30 @@
+process trycycler_cluster_subset {
+  tag "CLUSTERING SUBSET CONTIGS: ${barcode}"
+  container 'quay.io/biocontainers/trycycler:0.5.4--pyhdfd78af_0'
+
+  // Contigs can be filtered out to be < 2 within the process and segfault
+  errorStrategy { task.exitStatus == 1 ? 'ignore' : 'terminate' } 
+
+  input:
+  tuple val(barcode), path(unicycler_assembly), path(flye_assembly), path(trimmed_fq), val(num_contigs)
+
+  output:
+  tuple val(barcode), path("${barcode}_cluster/"), emit: clusters
+  tuple val(barcode), path("${barcode}_cluster/contigs.phylip"), emit: phylip
+
+  script:
+  """
+  mkdir assemblies
+  for d in ${unicycler_assembly} ${flye_assembly}
+  do
+    cp \$d/assembly.fasta assemblies/\$(basename \$d).fasta
+  done
+
+  trycycler cluster \\
+    --assemblies assemblies/*.fasta \\
+    --reads $trimmed_fq \\
+    --out_dir ${barcode}_cluster \\
+    --min_contig_len ${params.trycycler_min_contig_length} \\
+    --threads ${task.cpus}
+  """
+}
diff --git a/modules/run_trycycler_subsample.nf b/modules/run_trycycler_subsample.nf
new file mode 100755
index 0000000..90a994e
--- /dev/null
+++ b/modules/run_trycycler_subsample.nf
@@ -0,0 +1,20 @@
+process trycycler_subsample {
+  tag "SUBSAMPLING FASTQS: ${barcode}"
+  container 'quay.io/biocontainers/trycycler:0.5.4--pyhdfd78af_0'
+
+  input:
+  tuple val(barcode), path(trimmed_fq)
+
+  output:
+  tuple val(barcode), path("${barcode}_subsets/*.fastq"), emit: subsets
+
+  script:
+  n_subsamples = params.subsamples.toInteger()
+  """
+  trycycler subsample \\
+    --reads $trimmed_fq \\
+    --out_dir ${barcode}_subsets \\
+    --count ${n_subsamples} \\
+    --threads ${task.cpus}
+  """
+}
diff --git a/modules/run_unicycler_subset.nf b/modules/run_unicycler_subset.nf
new file mode 100755
index 0000000..18a995d
--- /dev/null
+++ b/modules/run_unicycler_subset.nf
@@ -0,0 +1,21 @@
+process unicycler_assembly_subset {
+  tag "ASSEMBLING SUBSET GENOME WITH UNICYCLER: ${barcode}"
+  container 'quay.io/biocontainers/unicycler:0.4.8--py38h8162308_3'
+  //publishDir "${params.outDir}", mode: 'symlink'
+
+  input:
+  tuple val(barcode), path(subset_fq)
+
+  output:
+  tuple val(barcode), path("*"), emit: unicycler_assembly
+
+  script: 
+  """
+  SUBSAMPLEIDX=\$(basename ${subset_fq} .fastq | cut -d '_' -f 2)
+  OUTDIR="${barcode}_\${SUBSAMPLEIDX}_unicycler_assembly"
+  unicycler \
+    --long ${subset_fq} \\
+    --threads ${task.cpus} \\
+    --out \${OUTDIR}
+  """
+}
diff --git a/nextflow.config b/nextflow.config
index 4d3ed58..ce08d04 100755
--- a/nextflow.config
+++ b/nextflow.config
@@ -45,6 +45,8 @@ params {
 	plassembler_db = ''
 	amrfinderplus_db = ''
 	trycycler_min_contig_length = '5000' // TODO confirm suitability of this default
+	consensus_method = 'autocycler'  // alt: 'trycycler'
+	subsamples = 4  // Set to 0 with trycycler to skip subsampling
   multiqc_config = "${projectDir}/assets/multiqc_config.yml"
   sequencing_summary = '' 
   pycoqc_header_file = "${projectDir}/assets/pycoqc_report_header.txt"
@@ -190,6 +192,105 @@ process {
 		memory = 190.Gb
 	}
 	
+	withName: 'flye_assembly_subset' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 190.Gb
+	}
+
+	withName: 'unicycler_assembly_subset' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 24
+		time = '10h'
+		memory = 190.Gb
+	}
+
+	withName: 'estimate_genome_size_raven' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 24
+		time = '10h'
+		memory = 190.Gb
+	}
+
+	withName: 'estimate_genome_size_seqtk' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 1
+		time = '1h'
+		memory = 10.Gb
+	}
+	
+	withName: 'trycycler_subsample' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+		disk = 20.Gb
+	}
+	
+	withName: 'autocycler_subsample' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+		disk = 10.Gb
+	}
+	
+	withName: 'autocycler_compress' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+		disk = 10.Gb
+	}
+	
+	withName: 'autocycler_cluster' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+	}
+	
+	withName: 'autocycler_trim' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+	}
+	
+	withName: 'autocycler_resolve' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 1
+		time = '1h'
+		memory = 10.Gb
+	}
+	
+	withName: 'autocycler_combine' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 1
+		time = '1h'
+		memory = 10.Gb
+	}
+	
+	withName: 'autocycler_table' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 1
+		time = '1h'
+		memory = 10.Gb
+	}
+	
 	withName: 'trycycler_cluster' {	
 		executor = 'pbspro'
 		queue = 'normal'
@@ -197,6 +298,15 @@ process {
 		time = '10h'
 		memory = 20.Gb
 	}
+	
+	withName: 'trycycler_cluster_subset' {	
+		executor = 'pbspro'
+		queue = 'normal'
+		cpus = 4
+		time = '10h'
+		memory = 20.Gb
+		disk = 10.Gb
+	}
 
  	withName: 'trycycler_reconcile' {
     executor = 'pbspro'
diff --git a/subworkflows/autocycler.nf b/subworkflows/autocycler.nf
new file mode 100644
index 0000000..3d52c52
--- /dev/null
+++ b/subworkflows/autocycler.nf
@@ -0,0 +1,82 @@
+#!/usr/bin/env nextflow
+
+// To use DSL-2 will need to include this
+nextflow.enable.dsl=2
+
+// Import processes or subworkflows to be run in the workflow
+include { estimate_genome_size } from '../subworkflows/estimate_genome_size'
+include { autocycler_subsample } from '../modules/run_autocycler_subsample'
+include { flye_assembly_subset } from '../modules/run_flye_subset'
+include { unicycler_assembly_subset } from '../modules/run_unicycler_subset'
+include { autocycler_compress } from '../modules/run_autocycler_compress'
+include { autocycler_cluster } from '../modules/run_autocycler_cluster'
+include { autocycler_trim } from '../modules/run_autocycler_trim'
+include { autocycler_resolve } from '../modules/run_autocycler_resolve'
+include { autocycler_combine } from '../modules/run_autocycler_combine'
+include { autocycler_table } from '../modules/run_autocycler_table'
+
+
+workflow autocycler {
+    take:
+    trimmed_fq
+
+    main:
+    // ESTIMATE GENOME SIZE
+    estimate_genome_size(trimmed_fq)
+
+    autocycler_inputs = trimmed_fq
+        .join(estimate_genome_size.out.genome_size, by:0)
+
+    // SUBSAMPLE FASTQ FILES
+    autocycler_subsample(autocycler_inputs)
+
+    // Expand the multiple FASTQs per barcode to one tuple per subset
+    subsets = autocycler_subsample.out.subsets.transpose()
+
+    // DE NOVO GENOME ASSEMBLIES
+    flye_assembly_subset(subsets)
+    unicycler_assembly_subset(subsets)
+
+    all_assembly_dirs = flye_assembly_subset.out.flye_assembly
+        .mix(unicycler_assembly_subset.out.unicycler_assembly)
+        .groupTuple(by:0)
+
+    autocycler_compress(all_assembly_dirs)
+
+    autocycler_cluster(autocycler_compress.out.compressed)
+
+    clusters_for_refinement = autocycler_cluster.out.pass_clusters
+        .transpose()
+        .map { barcode, cluster_dir ->
+        cluster_id = cluster_dir.baseName
+        return [ barcode, cluster_id, cluster_dir ]
+        }
+
+    autocycler_trim(clusters_for_refinement)
+
+    autocycler_resolve(autocycler_trim.out.trimmed_cluster)
+
+    autocycler_resolve_out = autocycler_resolve.out.resolved_cluster
+        .groupTuple(by:0)
+
+    autocycler_combine_inputs = autocycler_cluster.out.cluster_out
+        .join(autocycler_resolve_out, by:0)
+        .map { barcode, autocycler_cluster_dir, cluster_ids, pass_cluster_dirs ->
+        [ barcode, autocycler_cluster_dir, pass_cluster_dirs ]
+        }
+
+    autocycler_combine(autocycler_combine_inputs)
+
+    autocycler_table(autocycler_combine.out.autocycler_out)
+
+    consensus =
+        autocycler_combine.out.autocycler_out
+        .map { barcode, autocycler_dir ->
+            String assembler = "consensus"
+            def consensus_fa = autocycler_dir / "consensus_assembly.fasta"
+            return [barcode, assembler, consensus_fa]
+        }
+
+    emit:
+    polished_consensus_per_barcode = consensus
+}
\ No newline at end of file
diff --git a/subworkflows/estimate_genome_size.nf b/subworkflows/estimate_genome_size.nf
new file mode 100755
index 0000000..30cef4a
--- /dev/null
+++ b/subworkflows/estimate_genome_size.nf
@@ -0,0 +1,43 @@
+process estimate_genome_size_raven {
+  tag "ESTIMATE GENOME SIZE: ${barcode}"
+  container 'quay.io/biocontainers/raven-assembler:1.8.3--h5ca1c30_3'
+
+  input:
+  tuple val(barcode), path(trimmed_fq)
+
+  output:
+  tuple val(barcode), path("raven.fasta"), emit: raven_fa
+
+  script:
+  """
+  raven --threads ${task.cpus} --disable-checkpoints ${trimmed_fq} > raven.fasta
+  """
+}
+
+process estimate_genome_size_seqtk {
+  tag "ESTIMATE GENOME SIZE: ${barcode}"
+  container 'quay.io/biocontainers/seqtk:1.4--h577a1d6_3'
+
+  input:
+  tuple val(barcode), path(raven_fa)
+
+  output:
+  tuple val(barcode), path("${barcode}_genome_size.txt"), emit: genome_size
+
+  script:
+  """
+  seqtk size ${raven_fa} | cut -f 2 > ${barcode}_genome_size.txt
+  """
+}
+
+workflow estimate_genome_size {
+  take:
+  trimmed_fq
+
+  main:
+  estimate_genome_size_raven(trimmed_fq)
+  estimate_genome_size_seqtk(estimate_genome_size_raven.out.raven_fa)
+
+  emit:
+  genome_size = estimate_genome_size_seqtk.out.genome_size
+}
\ No newline at end of file
diff --git a/subworkflows/trycycler.nf b/subworkflows/trycycler.nf
new file mode 100644
index 0000000..70f0fd4
--- /dev/null
+++ b/subworkflows/trycycler.nf
@@ -0,0 +1,219 @@
+#!/usr/bin/env nextflow
+
+// To use DSL-2 will need to include this
+nextflow.enable.dsl=2
+
+// Import processes or subworkflows to be run in the workflow
+include { trycycler_subsample } from '../modules/run_trycycler_subsample'
+include { flye_assembly_subset } from '../modules/run_flye_subset'
+include { unicycler_assembly_subset } from '../modules/run_unicycler_subset'
+include { trycycler_cluster } from '../modules/run_trycycler_cluster'
+include { trycycler_cluster_subset } from '../modules/run_trycycler_cluster_subset'
+include { classify_trycycler } from '../modules/run_trycycler_classify'
+include { trycycler_reconcile } from '../modules/run_trycycler_reconcile'
+include { trycycler_msa } from '../modules/run_trycycler_msa'
+include { trycycler_partition } from '../modules/run_trycycler_partition'
+include { trycycler_consensus} from '../modules/run_trycycler_consensus'
+include { medaka_polish_consensus } from '../modules/run_medaka_polish_consensus'
+include { concat_fastas } from '../modules/concat_fa'
+
+
+workflow trycycler {
+    take:
+    trimmed_fq
+    trycycler_assemblies
+
+    main:
+    if (params.subsamples.toInteger() > 1) {
+
+        // Subsampling to be performed
+        // SUBSAMPLE FASTQ FILES
+        trycycler_subsample(trimmed_fq)
+
+        // Expand the multiple FASTQs per barcode to one tuple per subset
+        subsets = trycycler_subsample.out.subsets.transpose()
+
+        // DE NOVO GENOME ASSEMBLIES
+        // We need to re-run the assemblies on each subset
+        flye_assembly_subset(subsets)
+        unicycler_assembly_subset(subsets)
+
+        /*
+        * CONSENSUS ASSEMBLY PRE-PROCESSING
+        * 
+        * Trycycler requires >= 3 contigs to run. Use the in-built .countFasta()
+        * operator to count the total number of contigs assembled for each barcode.
+        *
+        * If additional assemblers/read subsets/replicates are added, ensure it 
+        * is added here.
+        */
+        num_contigs_per_barcode =
+            unicycler_assembly_subset.out.unicycler_assembly
+            .mix(flye_assembly_subset.out.flye_assembly)
+            .map { barcode, assembly_dir ->
+                // Count num contigs per assembly
+                def fa = assembly_dir + "/assembly.fasta"
+                def ncontigs = fa.countFasta()
+                return [barcode, ncontigs]
+            }
+            .groupTuple()
+            .map { barcode, ncontigs ->
+                // Add total contigs across assemblies
+                def total_contigs = ncontigs.inject(0, { result, x -> result + x.toInteger() })
+                return [barcode, total_contigs]
+            }
+
+        unicycler_assembly_subset_grouped = unicycler_assembly_subset.out.unicycler_assembly
+            .groupTuple()
+        flye_assembly_subset_grouped = flye_assembly_subset.out.flye_assembly
+            .groupTuple()
+        denovo_assemblies =
+            unicycler_assembly_subset_grouped
+            .join(flye_assembly_subset_grouped, by:0)
+            .join(trimmed_fq, by:0)
+            .join(num_contigs_per_barcode, by:0)
+
+        denovo_assembly_contigs =
+            denovo_assemblies
+            .branch { barcode, unicycler, flye, trimmed_fastq, num_contigs ->
+                run_trycycler: num_contigs >= 3
+                skip_trycycler: num_contigs < 3
+            }
+
+        // RUN TRYCYCLER (CONSENSUS ASSEMBLY) IF SUFFICIENT # CONTIGS
+        // TRYCYCLER: Cluster contigs
+        trycycler_cluster_subset(denovo_assembly_contigs.run_trycycler)
+
+        barcode_cluster_sizes =
+            // trycycler_cluster filters out small contigs (default < 5000nt) and can
+            // result in < 2 contigs here again. Use the same branching logic to avoid
+            // < 2 contig errors.
+            trycycler_cluster_subset.out.phylip
+            .map { barcode, contigs_phylip ->
+                def phylip_lines = contigs_phylip.text.readLines().size - 1 // exclude header
+                return [barcode, phylip_lines]
+            }
+            .branch { barcode, phylip_lines ->
+                run_trycycler: phylip_lines >= 3
+                skip_trycycler: phylip_lines < 3
+                // create a channel from skip_trycycler if failed barcodes need to be reported/used
+            }
+
+        // TRYCYCLER: Classify clusters
+        clusters_to_classify =
+            // Add path to clusters with sufficient contigs
+            trycycler_cluster_subset.out.clusters
+            .join(barcode_cluster_sizes.run_trycycler)
+
+    } else {
+
+        // No subsampling
+        // Start by clustering the existing assemblies
+        trycycler_cluster(trycycler_assemblies)
+
+        // trycycler_cluster filters out small contigs (default < 5000nt) and can
+        // result in < 2 contigs here.
+        // Use branching logic to avoid < 2 contig errors.
+        barcode_cluster_sizes =
+            trycycler_cluster.out.phylip
+            .map { barcode, contigs_phylip ->
+                def phylip_lines = contigs_phylip.text.readLines().size - 1 // exclude header
+                return [barcode, phylip_lines]
+            }
+            .branch { barcode, phylip_lines ->
+                run_trycycler: phylip_lines >= 3
+                skip_trycycler: phylip_lines < 3
+            }
+
+        // TRYCYCLER: Classify clusters
+        clusters_to_classify =
+            // Add path to clusters with sufficient contigs
+            trycycler_cluster.out.clusters
+            .join(barcode_cluster_sizes.run_trycycler)
+
+    }
+
+    classify_trycycler(clusters_to_classify)
+
+    // TRYCYCLER: Reconcile contigs
+    clusters_to_reconcile_flat = 
+        classify_trycycler.out.clusters_to_reconcile
+        .join(trimmed_fq, by: 0)
+        // from [barcode, [pathA, pathB], ...]
+        // to [barcode, pathA, ...]; [barcode, pathB, ...]
+        .transpose()
+
+    trycycler_reconcile(clusters_to_reconcile_flat)
+
+    reconciled_cluster_dirs = 
+        trycycler_reconcile.out.reconciled_seqs // successfully reconciled
+        .map { barcode, seq ->
+            // drops the last part of the path (reconciled_seqs file) as trycycler
+            // searches the parent dir for it
+            Path cluster_dir = seq.getParent()
+            return [barcode, cluster_dir] 
+        }
+    
+    // TRYCYCLER: Align
+    trycycler_msa(reconciled_cluster_dirs)
+
+    // TRYCYCLER: Partitioning reads
+    clusters_to_partition =
+        trycycler_msa.out.results_dir
+        .groupTuple()
+        .join(trimmed_fq)
+
+    trycycler_partition(clusters_to_partition)
+
+    // TRYCYCLER: Generate consensus assemblies  
+    clusters_for_consensus =
+        trycycler_partition.out.partitioned_reads
+        .transpose() // "ungroup" tuple
+        .map { barcode, reads ->
+            // Get directories of successfully partitioned reads
+            Path cluster_dir = reads.getParent()
+            return [barcode, cluster_dir]
+        }
+
+    trycycler_consensus(clusters_for_consensus)
+
+    // MEDAKA: Polish consensus assembly
+    consensus_dir = 
+        // Get parent dir for assembly
+        trycycler_consensus.out.cluster_assembly
+        .map { barcode, assembly ->
+            Path assembly_dir = assembly.getParent()
+            return [barcode, assembly_dir]
+        }
+
+    medaka_polish_consensus(consensus_dir)
+
+    // CAT: Combine polished clusters consensus assemblies into a single fasta
+    polished_clusters = 
+        medaka_polish_consensus.out.cluster_assembly
+        .groupTuple()
+        .map { barcode, fastas ->
+            // Need to avoid filename collisions with consensus.fasta files
+            // by renaming them
+            def indexed_fas = 
+                fastas.withIndex()
+                .collect { fa, idx ->
+                    def new_fa = fa.getParent() / "consensus_${idx}.fasta"
+                    fa.copyTo(new_fa)
+                    return new_fa
+                }
+            return [barcode, indexed_fas]
+        }
+    
+    concat_fastas(polished_clusters)
+
+    consensus =
+        concat_fastas.out
+        .map { barcode, consensus_fa ->
+            String assembler = "consensus"
+            return [barcode, assembler, consensus_fa]
+        }
+
+    emit:
+    polished_consensus_per_barcode = consensus
+}
\ No newline at end of file


Reviews:
user: login: georgiesamaha
body: Looks great @mgeaghan! Everything ran smoothly. Have noted some hashed out code, unsure if we need or not. Will create a separate issue re: the impact some syntax within the autocycler subworkflows has had on multiqc report. Without barcode being added to file names, some values are overwriting each other. Multiqc report needs a clean up anyway.

Once docs changes have been incorporated, merge away!  
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T23:44:28Z

Review comments:
diff_hunk: @@ -0,0 +1,27 @@
+process autocycler_combine {
+  tag "AUTOCYCLER COMBINE: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+  publishDir "${params.outdir}/assemblies/${barcode}_consensus", mode: 'copy'
+
+  // errorStrategy { task.exitStatus == 1 ? 'ignore' : 'terminate' } 
path: modules/run_autocycler_combine.nf
user: login: georgiesamaha
body: Is this required or can we chuck it? 
created_at: 2025-05-20T23:29:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,27 @@
+process autocycler_combine {
+  tag "AUTOCYCLER COMBINE: ${barcode}"
+  container 'quay.io/biocontainers/autocycler:0.3.0--h3ab6199_0'
+  publishDir "${params.outdir}/assemblies/${barcode}_consensus", mode: 'copy'
+
+  // errorStrategy { task.exitStatus == 1 ? 'ignore' : 'terminate' } 
path: modules/run_autocycler_combine.nf
user: login: georgiesamaha
body: Same applies to: 

* `modules/run_autocycler_compress.nf`
* `modules/run_autocycler_resolve.nf`
* `modules/run_autocycler_subsample.nf`
* `modules/run_autocycler_table.nf`
created_at: 2025-05-20T23:40:13Z
author_association: MEMBER

PR comments:
user: login: mgeaghan
created_at: 2025-05-21T05:25:42Z
body: @georgiesamaha When you say there were over-written values in the multiqc, are you talking about the QUAST results? If so, I noticed this issue too and it's actually a problem present before these changes. Both unicycler and flye are getting called 'consensus' and only one gets reported in the MultiQC report. The actual consensus sequence from trycycler/autocycler is called `barcode##_combined` (trycycler) or `consensus_assembly` (autocycler). Agree that we need to address this either way.

Thanks for creating the ticket for it, I should have done that earlier but hadn't gotten around to it. I'll add my notes to that issue.

As for updated docs, I updated the `interpreting-results.md` file to address the new autocycler method. I also created a `getting-started.md` to explain the general command line usage, including the new parameters for controlling autocycler/trycycler and the subsampling.

If you're happy with what I've written I'll go ahead and merge.
author_association: CONTRIBUTOR
user: login: Mitchob
created_at: 2025-05-23T04:44:37Z
body: @mgeaghan nice work! I made a couple of spelling/grammar fixes in the `getting-started.md` and added a link to the Gadi documentation in the "Execution on gadi" section (commit [126706e](https://github.com/Sydney-Informatics-Hub/ONT-bacpac-nf/pull/76/commits/126706ebdf66db45d4f2c59e168dc6f4d97f5d33))

Should we add a "What to Expect After Running" or "Results" section? It could outline where results are stored (default: `results/`, configurable via `--outdir`) and link to `interpreting-results.md`. This might be a clearer alternative to the current "Next steps" section. 

author_association: CONTRIBUTOR
user: login: mgeaghan
created_at: 2025-05-23T06:24:04Z
body: @Mitchob Thanks for your changes. As you suggested, I added a brief 'outputs' section to run over the directories to expect and point to interpreting-results.md for more detail.
author_association: CONTRIBUTOR
user: login: mgeaghan
created_at: 2025-05-23T06:24:35Z
body: Also deleted the commented-out error strategies as per Georgie's suggestion
author_association: CONTRIBUTOR