From 09313af81aa90ba5cc715fd2921bbc5299d09bbd Mon Sep 17 00:00:00 2001 From: lyh970817 Date: Fri, 20 Mar 2026 23:12:12 +0800 Subject: [PATCH 1/5] feat: add gcta/adjustgrm module --- .../nf-core/gcta/adjustgrm/environment.yml | 7 + modules/nf-core/gcta/adjustgrm/main.nf | 42 ++++++ modules/nf-core/gcta/adjustgrm/meta.yml | 90 ++++++++++++ .../nf-core/gcta/adjustgrm/tests/main.nf.test | 128 ++++++++++++++++++ .../gcta/adjustgrm/tests/main.nf.test.snap | 103 ++++++++++++++ .../gcta/adjustgrm/tests/nextflow.config | 3 + 6 files changed, 373 insertions(+) create mode 100644 modules/nf-core/gcta/adjustgrm/environment.yml create mode 100644 modules/nf-core/gcta/adjustgrm/main.nf create mode 100644 modules/nf-core/gcta/adjustgrm/meta.yml create mode 100644 modules/nf-core/gcta/adjustgrm/tests/main.nf.test create mode 100644 modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap create mode 100644 modules/nf-core/gcta/adjustgrm/tests/nextflow.config diff --git a/modules/nf-core/gcta/adjustgrm/environment.yml b/modules/nf-core/gcta/adjustgrm/environment.yml new file mode 100644 index 000000000000..3e22ea7b9f20 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::gcta=1.94.1 diff --git a/modules/nf-core/gcta/adjustgrm/main.nf b/modules/nf-core/gcta/adjustgrm/main.nf new file mode 100644 index 000000000000..e807dfe150a9 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/main.nf @@ -0,0 +1,42 @@ +process GCTA_ADJUSTGRM { + tag "${meta.id}" + label 'process_medium' + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'docker://community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' : + 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' }" + + input: + tuple val(meta), path(grm_id), path(grm_bin), path(grm_n_bin) + val grm_adj + + output: + tuple val(meta), path("*_adj.grm.id"), path("*_adj.grm.bin"), path("*_adj.grm.N.bin"), emit: grm_files + tuple val("${task.process}"), val("gcta"), eval("gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//'"), emit: versions_gcta, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def grm_adj_value = (grm_adj == null || grm_adj == '') ? 0 : grm_adj + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + gcta \\ + --grm ${meta.id} \\ + --grm-adj ${grm_adj_value} \\ + --make-grm \\ + --out ${prefix}_adj \\ + --thread-num ${task.cpus} \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_adj.grm.id + touch ${prefix}_adj.grm.bin + touch ${prefix}_adj.grm.N.bin + """ +} diff --git a/modules/nf-core/gcta/adjustgrm/meta.yml b/modules/nf-core/gcta/adjustgrm/meta.yml new file mode 100644 index 000000000000..142b073797db --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/meta.yml @@ -0,0 +1,90 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "gcta_adjustgrm" +description: Adjust a dense GRM for incomplete tagging using `gcta --grm-adj` +keywords: + - gcta + - grm + - genetics +tools: + - "gcta": + description: "Genome-wide Complex Trait Analysis (GCTA) estimates genetic relationships, variance components, and association statistics from genome-wide data." + homepage: "https://yanglab.westlake.edu.cn/software/gcta/" + documentation: "https://yanglab.westlake.edu.cn/software/gcta/static/gcta_doc_latest.pdf" + tool_dev_url: "https://yanglab.westlake.edu.cn/software/gcta/" + +input: + - - meta: + type: map + description: | + Groovy map containing dense GRM metadata + e.g. `[ id:'plink_simulated' ]` + - grm_id: + type: file + description: Dense GRM sample identifier file + pattern: "*.grm.id" + ontologies: [] + - grm_bin: + type: file + description: Dense GRM binary matrix file + pattern: "*.grm.bin" + ontologies: [] + - grm_n_bin: + type: file + description: Dense GRM sample-count matrix file + pattern: "*.grm.N.bin" + ontologies: [] + - grm_adj: + type: integer + description: | + GRM adjustment value passed to `--grm-adj`. + When an empty string is supplied, the module falls back to `0`. + +output: + grm_files: + - - meta: + type: map + description: | + Groovy map containing dense GRM metadata + e.g. `[ id:'plink_simulated' ]` + - "*_adj.grm.id": + type: file + description: Adjusted GRM sample identifier file + pattern: "*_adj.grm.id" + ontologies: [] + - "*_adj.grm.bin": + type: file + description: Adjusted GRM binary matrix file + pattern: "*_adj.grm.bin" + ontologies: [] + - "*_adj.grm.N.bin": + type: file + description: Adjusted GRM sample-count matrix file + pattern: "*_adj.grm.N.bin" + ontologies: [] + versions_gcta: + - - "${task.process}": + type: string + description: The process the version was collected from + - "gcta": + type: string + description: The tool name + - "gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//'": + type: eval + description: The command used to retrieve the GCTA version + +topics: + versions: + - - ${task.process}: + type: string + description: The process the version was collected from + - gcta: + type: string + description: The tool name + - gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//': + type: eval + description: The command used to retrieve the GCTA version + +authors: + - "@andongni" +maintainers: + - "@andongni" diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test new file mode 100644 index 000000000000..947453dc0866 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test @@ -0,0 +1,128 @@ +nextflow_process { + + name "Test Process GCTA_ADJUSTGRM" + script "../main.nf" + process "GCTA_ADJUSTGRM" + + tag "modules" + tag "modules_nfcore" + tag "gcta" + tag "gcta/adjustgrm" + tag "gcta/makegrmpart" + + setup { + run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE") { + script "../../makegrmpart/main.nf" + process { + """ + file('plink_simulated.mbfile').text = 'plink_simulated\\n' + + input[0] = [ + [ id:'plink_simulated_dense', part_gcta_job:1, nparts_gcta:1 ], + file('plink_simulated.mbfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] + ] + input[1] = [[ id:'all_variants' ], []] + """ + } + } + } + + test("homo_sapiens popgen - adjust dense GRM") { + config "./nextflow.config" + + when { + process { + """ + dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } + + input[0] = dense_grm + input[1] = 1 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.grm_files.size() == 1 }, + { assert process.out.grm_files.get(0).get(0).id == "plink_simulated_dense.part_1_1" }, + { + assert snapshot( + process.out.grm_files, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() + } + ) + } + } + + test("homo_sapiens popgen - adjust dense GRM with fallback default") { + config "./nextflow.config" + + when { + process { + """ + dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } + + input[0] = dense_grm + input[1] = '' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.grm_files.size() == 1 }, + { assert process.out.grm_files.get(0).get(0).id == "plink_simulated_dense.part_1_1" }, + { + assert snapshot( + process.out.grm_files, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() + } + ) + } + } + + test("homo_sapiens popgen - adjust dense GRM - stub") { + options "-stub" + config "./nextflow.config" + + when { + process { + """ + dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } + + input[0] = dense_grm + input[1] = 1 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap new file mode 100644 index 000000000000..47867f8dcb57 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap @@ -0,0 +1,103 @@ +{ + "homo_sapiens popgen - adjust dense GRM - stub": { + "content": [ + { + "0": [ + [ + { + "id": "plink_simulated_dense.part_1_1" + }, + "plink_simulated_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", + "plink_simulated_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "GCTA_ADJUSTGRM", + "gcta", + "1.94.1" + ] + ], + "grm_files": [ + [ + { + "id": "plink_simulated_dense.part_1_1" + }, + "plink_simulated_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", + "plink_simulated_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_gcta": [ + [ + "GCTA_ADJUSTGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-13T15:35:38.715590031" + }, + "homo_sapiens popgen - adjust dense GRM": { + "content": [ + [ + [ + { + "id": "plink_simulated_dense.part_1_1" + }, + "plink_simulated_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "plink_simulated_dense.part_1_1_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", + "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + ] + ], + { + "versions_gcta": [ + [ + "GCTA_ADJUSTGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-16T23:53:57.519973105" + }, + "homo_sapiens popgen - adjust dense GRM with fallback default": { + "content": [ + [ + [ + { + "id": "plink_simulated_dense.part_1_1" + }, + "plink_simulated_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "plink_simulated_dense.part_1_1_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", + "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + ] + ], + { + "versions_gcta": [ + [ + "GCTA_ADJUSTGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-17T00:00:08.732593482" + } +} \ No newline at end of file diff --git a/modules/nf-core/gcta/adjustgrm/tests/nextflow.config b/modules/nf-core/gcta/adjustgrm/tests/nextflow.config new file mode 100644 index 000000000000..de31e0218829 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/tests/nextflow.config @@ -0,0 +1,3 @@ +params { + modules_testdata_base_path = System.getenv("NF_MODULES_TESTDATA_BASE_PATH") ?: "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/" +} From 88d6b14852c0387b7d1e718fe85f6c7e09f8ed0e Mon Sep 17 00:00:00 2001 From: lyh970817 Date: Sat, 21 Mar 2026 15:53:17 +0800 Subject: [PATCH 2/5] test(gcta/adjustgrm): harden basename contract coverage --- modules/nf-core/gcta/adjustgrm/main.nf | 2 +- modules/nf-core/gcta/adjustgrm/meta.yml | 19 +-- .../nf-core/gcta/adjustgrm/tests/main.nf.test | 135 ++++++++++++------ .../gcta/adjustgrm/tests/main.nf.test.snap | 63 +++----- 4 files changed, 123 insertions(+), 96 deletions(-) diff --git a/modules/nf-core/gcta/adjustgrm/main.nf b/modules/nf-core/gcta/adjustgrm/main.nf index e807dfe150a9..2bd49bf8e9df 100644 --- a/modules/nf-core/gcta/adjustgrm/main.nf +++ b/modules/nf-core/gcta/adjustgrm/main.nf @@ -19,7 +19,7 @@ process GCTA_ADJUSTGRM { script: def args = task.ext.args ?: '' - def grm_adj_value = (grm_adj == null || grm_adj == '') ? 0 : grm_adj + def grm_adj_value = grm_adj == null ? 0 : grm_adj def prefix = task.ext.prefix ?: "${meta.id}" """ diff --git a/modules/nf-core/gcta/adjustgrm/meta.yml b/modules/nf-core/gcta/adjustgrm/meta.yml index 142b073797db..0b5719308a39 100644 --- a/modules/nf-core/gcta/adjustgrm/meta.yml +++ b/modules/nf-core/gcta/adjustgrm/meta.yml @@ -16,36 +16,39 @@ input: - - meta: type: map description: | - Groovy map containing dense GRM metadata - e.g. `[ id:'plink_simulated' ]` + Groovy map containing dense GRM metadata. + `meta.id` is the required GRM basename consumed by `--grm` and must match + the staged dense GRM files. + e.g. `[ id:'tiny_dense' ]` requires + `tiny_dense.grm.id`, `tiny_dense.grm.bin`, and `tiny_dense.grm.N.bin`. - grm_id: type: file - description: Dense GRM sample identifier file + description: Dense GRM sample identifier file with basename `${meta.id}` pattern: "*.grm.id" ontologies: [] - grm_bin: type: file - description: Dense GRM binary matrix file + description: Dense GRM binary matrix file with basename `${meta.id}` pattern: "*.grm.bin" ontologies: [] - grm_n_bin: type: file - description: Dense GRM sample-count matrix file + description: Dense GRM sample-count matrix file with basename `${meta.id}` pattern: "*.grm.N.bin" ontologies: [] - grm_adj: type: integer description: | GRM adjustment value passed to `--grm-adj`. - When an empty string is supplied, the module falls back to `0`. + If not provided (`null`), the module falls back to integer `0`. output: grm_files: - - meta: type: map description: | - Groovy map containing dense GRM metadata - e.g. `[ id:'plink_simulated' ]` + Groovy map containing dense GRM metadata. + `meta.id` remains the dense-GRM basename contract used for `--grm`. - "*_adj.grm.id": type: file description: Adjusted GRM sample identifier file diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test index 947453dc0866..13f09884fe85 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test @@ -8,33 +8,6 @@ nextflow_process { tag "modules_nfcore" tag "gcta" tag "gcta/adjustgrm" - tag "gcta/makegrmpart" - - setup { - run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE") { - script "../../makegrmpart/main.nf" - process { - """ - file('plink_simulated.mbfile').text = 'plink_simulated\\n' - - input[0] = [ - [ id:'plink_simulated_dense', part_gcta_job:1, nparts_gcta:1 ], - file('plink_simulated.mbfile'), - [ - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) - ], - [ - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) - ], - [ - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) - ] - ] - input[1] = [[ id:'all_variants' ], []] - """ - } - } - } test("homo_sapiens popgen - adjust dense GRM") { config "./nextflow.config" @@ -42,12 +15,11 @@ nextflow_process { when { process { """ - dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } + file('tiny_dense.grm.id').text = 'sample1 sample1\\n' + file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() + file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - input[0] = dense_grm + input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] input[1] = 1 """ } @@ -57,7 +29,14 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "plink_simulated_dense.part_1_1" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { + def grm_row = process.out.grm_files.get(0) + def expected_prefix = "${grm_row.get(0).id}_adj" + assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" + assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" + assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + }, { assert snapshot( process.out.grm_files, @@ -68,19 +47,18 @@ nextflow_process { } } - test("homo_sapiens popgen - adjust dense GRM with fallback default") { + test("homo_sapiens popgen - adjust dense GRM with 0 fallback value") { config "./nextflow.config" when { process { """ - dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } + file('tiny_dense.grm.id').text = 'sample1 sample1\\n' + file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() + file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - input[0] = dense_grm - input[1] = '' + input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[1] = 0 """ } } @@ -89,7 +67,14 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "plink_simulated_dense.part_1_1" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { + def grm_row = process.out.grm_files.get(0) + def expected_prefix = "${grm_row.get(0).id}_adj" + assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" + assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" + assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + }, { assert snapshot( process.out.grm_files, @@ -100,6 +85,50 @@ nextflow_process { } } + test("homo_sapiens popgen - adjust dense GRM fails when meta.id is not GRM basename") { + config "./nextflow.config" + + when { + process { + """ + file('tiny_dense.grm.id').text = 'sample1 sample1\\n' + file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() + file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() + + input[0] = [[ id:'tiny_dense_mismatched' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[1] = 1 + """ + } + } + + then { + assertAll( + { assert !process.success }, + { assert process.exitStatus != 0 } + ) + } + } + + test("homo_sapiens popgen - adjust dense GRM fails for malformed GRM tuple") { + config "./nextflow.config" + + when { + process { + """ + file('tiny_dense.grm.id').text = 'sample1 sample1\\n' + file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() + + input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin')] + input[1] = 1 + """ + } + } + + then { + assert !process.success + } + } + test("homo_sapiens popgen - adjust dense GRM - stub") { options "-stub" config "./nextflow.config" @@ -107,12 +136,11 @@ nextflow_process { when { process { """ - dense_grm = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } + file('tiny_dense.grm.id').text = 'sample1 sample1\\n' + file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() + file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - input[0] = dense_grm + input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] input[1] = 1 """ } @@ -121,7 +149,20 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { + def grm_row = process.out.grm_files.get(0) + def expected_prefix = "${grm_row.get(0).id}_adj" + assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" + assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" + assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + }, + { + assert snapshot( + process.out.grm_files, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() + } ) } } diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap index 47867f8dcb57..85d1f67eec8f 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap @@ -1,34 +1,17 @@ { "homo_sapiens popgen - adjust dense GRM - stub": { "content": [ + [ + [ + { + "id": "tiny_dense" + }, + "tiny_dense_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", + "tiny_dense_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "tiny_dense_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], { - "0": [ - [ - { - "id": "plink_simulated_dense.part_1_1" - }, - "plink_simulated_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", - "plink_simulated_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", - "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - "GCTA_ADJUSTGRM", - "gcta", - "1.94.1" - ] - ], - "grm_files": [ - [ - { - "id": "plink_simulated_dense.part_1_1" - }, - "plink_simulated_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", - "plink_simulated_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", - "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], "versions_gcta": [ [ "GCTA_ADJUSTGRM", @@ -42,18 +25,18 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-13T15:35:38.715590031" + "timestamp": "2026-03-21T00:37:38.261527898" }, - "homo_sapiens popgen - adjust dense GRM": { + "homo_sapiens popgen - adjust dense GRM with 0 fallback value": { "content": [ [ [ { - "id": "plink_simulated_dense.part_1_1" + "id": "tiny_dense" }, - "plink_simulated_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "plink_simulated_dense.part_1_1_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", - "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + "tiny_dense_adj.grm.id:md5,822e827e7c1bf900290ef807f514b94d", + "tiny_dense_adj.grm.bin:md5,5021d5886f533f04ba4abcddb8ca2c6b", + "tiny_dense_adj.grm.N.bin:md5,a5d1e9463fae706307f90b05e9e6db9a" ] ], { @@ -70,18 +53,18 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-16T23:53:57.519973105" + "timestamp": "2026-03-21T00:31:54.524773747" }, - "homo_sapiens popgen - adjust dense GRM with fallback default": { + "homo_sapiens popgen - adjust dense GRM": { "content": [ [ [ { - "id": "plink_simulated_dense.part_1_1" + "id": "tiny_dense" }, - "plink_simulated_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "plink_simulated_dense.part_1_1_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", - "plink_simulated_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + "tiny_dense_adj.grm.id:md5,822e827e7c1bf900290ef807f514b94d", + "tiny_dense_adj.grm.bin:md5,5021d5886f533f04ba4abcddb8ca2c6b", + "tiny_dense_adj.grm.N.bin:md5,a5d1e9463fae706307f90b05e9e6db9a" ] ], { @@ -98,6 +81,6 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-17T00:00:08.732593482" + "timestamp": "2026-03-21T00:36:32.857901189" } } \ No newline at end of file From 5ffd6089af6cd3b0c455d6e5bd9960eddf0de8f1 Mon Sep 17 00:00:00 2001 From: lyh970817 Date: Sat, 21 Mar 2026 18:47:04 +0800 Subject: [PATCH 3/5] test(gcta/adjustgrm): generate GRM inputs in setup --- .../adjustgrm/tests/helpers/dense_grm/main.nf | 33 ++++++++++ .../nf-core/gcta/adjustgrm/tests/main.nf.test | 66 ++++++++++++------- .../gcta/adjustgrm/tests/main.nf.test.snap | 26 ++++---- 3 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf diff --git a/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf b/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf new file mode 100644 index 000000000000..195829aa55d4 --- /dev/null +++ b/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf @@ -0,0 +1,33 @@ +process GCTA_TEST_DENSE_GRM { + tag "${meta.id}" + label "process_medium" + conda "${projectDir}/modules/nf-core/gcta/adjustgrm/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'docker://community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' : + 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9'}" + + input: + tuple val(meta), path(bed), path(bim), path(fam) + + output: + tuple val(meta), path("${meta.id}.grm.id"), path("${meta.id}.grm.bin"), path("${meta.id}.grm.N.bin"), emit: dense_grm + + script: + def bfile_prefix = bed.baseName + """ + set -euo pipefail + + gcta \\ + --bfile "${bfile_prefix}" \\ + --make-grm \\ + --out "${meta.id}" \\ + --thread-num ${task.cpus} + """ + + stub: + """ + touch "${meta.id}.grm.id" + touch "${meta.id}.grm.bin" + touch "${meta.id}.grm.N.bin" + """ +} diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test index 13f09884fe85..be4db413b277 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test @@ -8,6 +8,37 @@ nextflow_process { tag "modules_nfcore" tag "gcta" tag "gcta/adjustgrm" + tag "tests/helpers/dense_grm" + + setup { + run("GCTA_TEST_DENSE_GRM", alias: "GCTA_TEST_DENSE_GRM_CONTRACT") { + script "../tests/helpers/dense_grm/main.nf" + process { + """ + input[0] = [ + [ id:'tiny_dense' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] + """ + } + } + + run("GCTA_TEST_DENSE_GRM", alias: "GCTA_TEST_DENSE_GRM_STUB") { + script "../tests/helpers/dense_grm/main.nf" + process { + """ + input[0] = [ + [ id:'stub_dense' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] + """ + } + } + } test("homo_sapiens popgen - adjust dense GRM") { config "./nextflow.config" @@ -15,11 +46,7 @@ nextflow_process { when { process { """ - file('tiny_dense.grm.id').text = 'sample1 sample1\\n' - file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() - file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - - input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm input[1] = 1 """ } @@ -53,11 +80,7 @@ nextflow_process { when { process { """ - file('tiny_dense.grm.id').text = 'sample1 sample1\\n' - file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() - file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - - input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm input[1] = 0 """ } @@ -91,11 +114,9 @@ nextflow_process { when { process { """ - file('tiny_dense.grm.id').text = 'sample1 sample1\\n' - file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() - file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - - input[0] = [[ id:'tiny_dense_mismatched' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm.map { meta, grm_id, grm_bin, grm_n_bin -> + [[ id:'tiny_dense_mismatched' ], grm_id, grm_bin, grm_n_bin] + } input[1] = 1 """ } @@ -115,10 +136,9 @@ nextflow_process { when { process { """ - file('tiny_dense.grm.id').text = 'sample1 sample1\\n' - file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() - - input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin')] + input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm.map { meta, grm_id, grm_bin, grm_n_bin -> + [[ id:meta.id ], grm_id, grm_bin] + } input[1] = 1 """ } @@ -136,11 +156,7 @@ nextflow_process { when { process { """ - file('tiny_dense.grm.id').text = 'sample1 sample1\\n' - file('tiny_dense.grm.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.0f).array() - file('tiny_dense.grm.N.bin').bytes = java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(100.0f).array() - - input[0] = [[ id:'tiny_dense' ], file('tiny_dense.grm.id'), file('tiny_dense.grm.bin'), file('tiny_dense.grm.N.bin')] + input[0] = GCTA_TEST_DENSE_GRM_STUB.out.dense_grm input[1] = 1 """ } @@ -149,7 +165,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { assert process.out.grm_files.get(0).get(0).id == "stub_dense" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap index 85d1f67eec8f..66929b495375 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap @@ -4,11 +4,11 @@ [ [ { - "id": "tiny_dense" + "id": "stub_dense" }, - "tiny_dense_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", - "tiny_dense_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", - "tiny_dense_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + "stub_dense_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], { @@ -25,7 +25,7 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T00:37:38.261527898" + "timestamp": "2026-03-21T18:41:16.135785752" }, "homo_sapiens popgen - adjust dense GRM with 0 fallback value": { "content": [ @@ -34,9 +34,9 @@ { "id": "tiny_dense" }, - "tiny_dense_adj.grm.id:md5,822e827e7c1bf900290ef807f514b94d", - "tiny_dense_adj.grm.bin:md5,5021d5886f533f04ba4abcddb8ca2c6b", - "tiny_dense_adj.grm.N.bin:md5,a5d1e9463fae706307f90b05e9e6db9a" + "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "tiny_dense_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", + "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" ] ], { @@ -53,7 +53,7 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T00:31:54.524773747" + "timestamp": "2026-03-21T18:40:48.742341357" }, "homo_sapiens popgen - adjust dense GRM": { "content": [ @@ -62,9 +62,9 @@ { "id": "tiny_dense" }, - "tiny_dense_adj.grm.id:md5,822e827e7c1bf900290ef807f514b94d", - "tiny_dense_adj.grm.bin:md5,5021d5886f533f04ba4abcddb8ca2c6b", - "tiny_dense_adj.grm.N.bin:md5,a5d1e9463fae706307f90b05e9e6db9a" + "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "tiny_dense_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", + "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" ] ], { @@ -81,6 +81,6 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T00:36:32.857901189" + "timestamp": "2026-03-21T18:40:37.929577002" } } \ No newline at end of file From 4e8caa6028b4f467030acf6c830c904c6bc75e66 Mon Sep 17 00:00:00 2001 From: lyh970817 Date: Sat, 21 Mar 2026 20:47:17 +0800 Subject: [PATCH 4/5] tests: replace adjustgrm helper with makegrmpart --- .../adjustgrm/tests/helpers/dense_grm/main.nf | 33 -------- .../nf-core/gcta/adjustgrm/tests/main.nf.test | 77 +++++++++++++------ .../gcta/adjustgrm/tests/main.nf.test.snap | 30 ++++---- 3 files changed, 69 insertions(+), 71 deletions(-) delete mode 100644 modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf diff --git a/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf b/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf deleted file mode 100644 index 195829aa55d4..000000000000 --- a/modules/nf-core/gcta/adjustgrm/tests/helpers/dense_grm/main.nf +++ /dev/null @@ -1,33 +0,0 @@ -process GCTA_TEST_DENSE_GRM { - tag "${meta.id}" - label "process_medium" - conda "${projectDir}/modules/nf-core/gcta/adjustgrm/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'docker://community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' : - 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9'}" - - input: - tuple val(meta), path(bed), path(bim), path(fam) - - output: - tuple val(meta), path("${meta.id}.grm.id"), path("${meta.id}.grm.bin"), path("${meta.id}.grm.N.bin"), emit: dense_grm - - script: - def bfile_prefix = bed.baseName - """ - set -euo pipefail - - gcta \\ - --bfile "${bfile_prefix}" \\ - --make-grm \\ - --out "${meta.id}" \\ - --thread-num ${task.cpus} - """ - - stub: - """ - touch "${meta.id}.grm.id" - touch "${meta.id}.grm.bin" - touch "${meta.id}.grm.N.bin" - """ -} diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test index be4db413b277..329ff9a10f60 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test @@ -8,33 +8,53 @@ nextflow_process { tag "modules_nfcore" tag "gcta" tag "gcta/adjustgrm" - tag "tests/helpers/dense_grm" + tag "gcta/makegrmpart" setup { - run("GCTA_TEST_DENSE_GRM", alias: "GCTA_TEST_DENSE_GRM_CONTRACT") { - script "../tests/helpers/dense_grm/main.nf" + run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE") { + script "../../makegrmpart/main.nf" process { """ + file('plink_simulated.mbfile').text = 'plink_simulated\\n' + input[0] = [ - [ id:'tiny_dense' ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + [ id:'tiny_dense', part_gcta_job:1, nparts_gcta:1 ], + file('plink_simulated.mbfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] ] + input[1] = [[ id:'all_variants' ], []] """ } } - run("GCTA_TEST_DENSE_GRM", alias: "GCTA_TEST_DENSE_GRM_STUB") { - script "../tests/helpers/dense_grm/main.nf" + run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE_STUB") { + script "../../makegrmpart/main.nf" process { """ + file('plink_simulated.mbfile').text = 'plink_simulated\\n' + input[0] = [ - [ id:'stub_dense' ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + [ id:'stub_dense', part_gcta_job:1, nparts_gcta:1 ], + file('plink_simulated.mbfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] ] + input[1] = [[ id:'all_variants' ], []] """ } } @@ -46,7 +66,10 @@ nextflow_process { when { process { """ - input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm + input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } input[1] = 1 """ } @@ -56,7 +79,7 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense.part_1_1" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" @@ -80,7 +103,10 @@ nextflow_process { when { process { """ - input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm + input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } input[1] = 0 """ } @@ -90,7 +116,7 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense.part_1_1" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" @@ -114,8 +140,9 @@ nextflow_process { when { process { """ - input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm.map { meta, grm_id, grm_bin, grm_n_bin -> - [[ id:'tiny_dense_mismatched' ], grm_id, grm_bin, grm_n_bin] + input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix + '_mismatched' ], grm_id, grm_bin, grm_n_bin] } input[1] = 1 """ @@ -136,8 +163,9 @@ nextflow_process { when { process { """ - input[0] = GCTA_TEST_DENSE_GRM_CONTRACT.out.dense_grm.map { meta, grm_id, grm_bin, grm_n_bin -> - [[ id:meta.id ], grm_id, grm_bin] + input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin] } input[1] = 1 """ @@ -156,7 +184,10 @@ nextflow_process { when { process { """ - input[0] = GCTA_TEST_DENSE_GRM_STUB.out.dense_grm + input[0] = GCTA_MAKEGRMPART_DENSE_STUB.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> + def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job + [[ id:prefix ], grm_id, grm_bin, grm_n_bin] + } input[1] = 1 """ } @@ -165,7 +196,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert process.out.grm_files.get(0).get(0).id == "stub_dense" }, + { assert process.out.grm_files.get(0).get(0).id == "stub_dense.part_1_1" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap index 66929b495375..3616f96e5734 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap @@ -4,11 +4,11 @@ [ [ { - "id": "stub_dense" + "id": "stub_dense.part_1_1" }, - "stub_dense_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", - "stub_dense_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", - "stub_dense_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + "stub_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], { @@ -25,18 +25,18 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T18:41:16.135785752" + "timestamp": "2026-03-21T20:39:13.62183461" }, "homo_sapiens popgen - adjust dense GRM with 0 fallback value": { "content": [ [ [ { - "id": "tiny_dense" + "id": "tiny_dense.part_1_1" }, - "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "tiny_dense_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", - "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + "tiny_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "tiny_dense.part_1_1_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", + "tiny_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" ] ], { @@ -53,18 +53,18 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T18:40:48.742341357" + "timestamp": "2026-03-21T20:38:46.865665471" }, "homo_sapiens popgen - adjust dense GRM": { "content": [ [ [ { - "id": "tiny_dense" + "id": "tiny_dense.part_1_1" }, - "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "tiny_dense_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", - "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + "tiny_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", + "tiny_dense.part_1_1_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", + "tiny_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" ] ], { @@ -81,6 +81,6 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T18:40:37.929577002" + "timestamp": "2026-03-21T20:38:37.510503634" } } \ No newline at end of file From f5fd0465c00726222eba28d0a853b5d1eb24288b Mon Sep 17 00:00:00 2001 From: lyh970817 Date: Sat, 16 May 2026 15:51:56 +0800 Subject: [PATCH 5/5] Use makegrm setup for gcta/adjustgrm --- modules/nf-core/gcta/adjustgrm/main.nf | 18 +-- modules/nf-core/gcta/adjustgrm/meta.yml | 54 +++---- .../nf-core/gcta/adjustgrm/tests/main.nf.test | 78 ++++------ .../gcta/adjustgrm/tests/main.nf.test.snap | 40 ++--- modules/nf-core/gcta/makegrm/environment.yml | 7 + modules/nf-core/gcta/makegrm/main.nf | 42 ++++++ modules/nf-core/gcta/makegrm/meta.yml | 91 +++++++++++ .../nf-core/gcta/makegrm/tests/main.nf.test | 142 ++++++++++++++++++ .../gcta/makegrm/tests/main.nf.test.snap | 111 ++++++++++++++ .../gcta/makegrm/tests/nextflow.config | 3 + 10 files changed, 478 insertions(+), 108 deletions(-) create mode 100644 modules/nf-core/gcta/makegrm/environment.yml create mode 100644 modules/nf-core/gcta/makegrm/main.nf create mode 100644 modules/nf-core/gcta/makegrm/meta.yml create mode 100644 modules/nf-core/gcta/makegrm/tests/main.nf.test create mode 100644 modules/nf-core/gcta/makegrm/tests/main.nf.test.snap create mode 100644 modules/nf-core/gcta/makegrm/tests/nextflow.config diff --git a/modules/nf-core/gcta/adjustgrm/main.nf b/modules/nf-core/gcta/adjustgrm/main.nf index 2bd49bf8e9df..acae3ba18d15 100644 --- a/modules/nf-core/gcta/adjustgrm/main.nf +++ b/modules/nf-core/gcta/adjustgrm/main.nf @@ -2,34 +2,34 @@ process GCTA_ADJUSTGRM { tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'docker://community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' : - 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/46/46b0d05f0daa47561d87d2a9cac5e51edc2c78e26f1bbab439c688386241a274/data' + : 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9'}" input: - tuple val(meta), path(grm_id), path(grm_bin), path(grm_n_bin) - val grm_adj + tuple val(meta), path(grm_files), val(grm_adj) output: - tuple val(meta), path("*_adj.grm.id"), path("*_adj.grm.bin"), path("*_adj.grm.N.bin"), emit: grm_files - tuple val("${task.process}"), val("gcta"), eval("gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//'"), emit: versions_gcta, topic: versions + tuple val(meta), path("*_adj.grm.*"), emit: grm_files + tuple val("${task.process}"), val("gcta"), eval("gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'"), emit: versions_gcta, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' + def extra_args = task.ext.args ?: '' def grm_adj_value = grm_adj == null ? 0 : grm_adj def prefix = task.ext.prefix ?: "${meta.id}" """ + gcta \\ --grm ${meta.id} \\ --grm-adj ${grm_adj_value} \\ --make-grm \\ --out ${prefix}_adj \\ --thread-num ${task.cpus} \\ - ${args} + ${extra_args} """ stub: diff --git a/modules/nf-core/gcta/adjustgrm/meta.yml b/modules/nf-core/gcta/adjustgrm/meta.yml index 0b5719308a39..a9ebd7663cdf 100644 --- a/modules/nf-core/gcta/adjustgrm/meta.yml +++ b/modules/nf-core/gcta/adjustgrm/meta.yml @@ -3,7 +3,9 @@ name: "gcta_adjustgrm" description: Adjust a dense GRM for incomplete tagging using `gcta --grm-adj` keywords: - gcta + - genome-wide complex trait analysis - grm + - genetic relationship matrix - genetics tools: - "gcta": @@ -11,6 +13,8 @@ tools: homepage: "https://yanglab.westlake.edu.cn/software/gcta/" documentation: "https://yanglab.westlake.edu.cn/software/gcta/static/gcta_doc_latest.pdf" tool_dev_url: "https://yanglab.westlake.edu.cn/software/gcta/" + licence: ["GPL-3.0-only"] + identifier: "biotools:gcta" input: - - meta: @@ -21,26 +25,16 @@ input: the staged dense GRM files. e.g. `[ id:'tiny_dense' ]` requires `tiny_dense.grm.id`, `tiny_dense.grm.bin`, and `tiny_dense.grm.N.bin`. - - grm_id: + - grm_files: type: file - description: Dense GRM sample identifier file with basename `${meta.id}` - pattern: "*.grm.id" + description: Dense GRM file bundle with basename `${meta.id}` + pattern: "*.grm.*" ontologies: [] - - grm_bin: - type: file - description: Dense GRM binary matrix file with basename `${meta.id}` - pattern: "*.grm.bin" - ontologies: [] - - grm_n_bin: - type: file - description: Dense GRM sample-count matrix file with basename `${meta.id}` - pattern: "*.grm.N.bin" - ontologies: [] - - grm_adj: - type: integer - description: | - GRM adjustment value passed to `--grm-adj`. - If not provided (`null`), the module falls back to integer `0`. + - grm_adj: + type: integer + description: | + GRM adjustment value passed to `--grm-adj`. + If not provided (`null`), the module falls back to integer `0`. output: grm_files: @@ -49,20 +43,10 @@ output: description: | Groovy map containing dense GRM metadata. `meta.id` remains the dense-GRM basename contract used for `--grm`. - - "*_adj.grm.id": - type: file - description: Adjusted GRM sample identifier file - pattern: "*_adj.grm.id" - ontologies: [] - - "*_adj.grm.bin": - type: file - description: Adjusted GRM binary matrix file - pattern: "*_adj.grm.bin" - ontologies: [] - - "*_adj.grm.N.bin": + - "*_adj.grm.*": type: file - description: Adjusted GRM sample-count matrix file - pattern: "*_adj.grm.N.bin" + description: Adjusted GRM file bundle + pattern: "*_adj.grm.*" ontologies: [] versions_gcta: - - "${task.process}": @@ -71,7 +55,7 @@ output: - "gcta": type: string description: The tool name - - "gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//'": + - "gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'": type: eval description: The command used to retrieve the GCTA version @@ -83,11 +67,11 @@ topics: - gcta: type: string description: The tool name - - gcta --version 2>&1 | grep 'version v' | tr -s ' ' | cut -d' ' -f3 | sed 's/^v//': + - "gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'": type: eval description: The command used to retrieve the GCTA version authors: - - "@andongni" + - "@lyh970817" maintainers: - - "@andongni" + - "@lyh970817" diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test index 329ff9a10f60..5f848c95230f 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test @@ -8,17 +8,17 @@ nextflow_process { tag "modules_nfcore" tag "gcta" tag "gcta/adjustgrm" - tag "gcta/makegrmpart" + tag "gcta/makegrm" setup { - run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE") { - script "../../makegrmpart/main.nf" + run("GCTA_MAKEGRM", alias: "GCTA_MAKEGRM_DENSE") { + script "../../makegrm/main.nf" process { """ file('plink_simulated.mbfile').text = 'plink_simulated\\n' input[0] = [ - [ id:'tiny_dense', part_gcta_job:1, nparts_gcta:1 ], + [ id:'tiny_dense' ], file('plink_simulated.mbfile'), [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) @@ -30,19 +30,18 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) ] ] - input[1] = [[ id:'all_variants' ], []] """ } } - run("GCTA_MAKEGRMPART", alias: "GCTA_MAKEGRMPART_DENSE_STUB") { - script "../../makegrmpart/main.nf" + run("GCTA_MAKEGRM", alias: "GCTA_MAKEGRM_DENSE_STUB") { + script "../../makegrm/main.nf" process { """ file('plink_simulated.mbfile').text = 'plink_simulated\\n' input[0] = [ - [ id:'stub_dense', part_gcta_job:1, nparts_gcta:1 ], + [ id:'stub_dense' ], file('plink_simulated.mbfile'), [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) @@ -54,7 +53,6 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) ] ] - input[1] = [[ id:'all_variants' ], []] """ } } @@ -66,11 +64,7 @@ nextflow_process { when { process { """ - input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } - input[1] = 1 + input[0] = GCTA_MAKEGRM_DENSE.out.grm_files.map { meta, grm_files -> [meta, grm_files, 1] } """ } } @@ -79,13 +73,15 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "tiny_dense.part_1_1" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" - assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" - assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" - assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + assert grm_row.get(1).collect { file(it).name }.sort() == [ + "${expected_prefix}.grm.N.bin", + "${expected_prefix}.grm.bin", + "${expected_prefix}.grm.id" + ] }, { assert snapshot( @@ -97,17 +93,13 @@ nextflow_process { } } - test("homo_sapiens popgen - adjust dense GRM with 0 fallback value") { + test("homo_sapiens popgen - adjust dense GRM with null fallback value") { config "./nextflow.config" when { process { """ - input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } - input[1] = 0 + input[0] = GCTA_MAKEGRM_DENSE.out.grm_files.map { meta, grm_files -> [meta, grm_files, null] } """ } } @@ -116,13 +108,15 @@ nextflow_process { assertAll( { assert process.success }, { assert process.out.grm_files.size() == 1 }, - { assert process.out.grm_files.get(0).get(0).id == "tiny_dense.part_1_1" }, + { assert process.out.grm_files.get(0).get(0).id == "tiny_dense" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" - assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" - assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" - assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + assert grm_row.get(1).collect { file(it).name }.sort() == [ + "${expected_prefix}.grm.N.bin", + "${expected_prefix}.grm.bin", + "${expected_prefix}.grm.id" + ] }, { assert snapshot( @@ -140,11 +134,7 @@ nextflow_process { when { process { """ - input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix + '_mismatched' ], grm_id, grm_bin, grm_n_bin] - } - input[1] = 1 + input[0] = GCTA_MAKEGRM_DENSE.out.grm_files.map { meta, grm_files -> [[ id:meta.id + '_mismatched' ], grm_files, 1] } """ } } @@ -163,11 +153,7 @@ nextflow_process { when { process { """ - input[0] = GCTA_MAKEGRMPART_DENSE.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin] - } - input[1] = 1 + input[0] = GCTA_MAKEGRM_DENSE.out.grm_files.map { meta, grm_files -> [meta, 1] } """ } } @@ -184,11 +170,7 @@ nextflow_process { when { process { """ - input[0] = GCTA_MAKEGRMPART_DENSE_STUB.out.grm_files.map { meta, grm_id, grm_bin, grm_n_bin -> - def prefix = meta.id + '.part_' + meta.nparts_gcta + '_' + meta.part_gcta_job - [[ id:prefix ], grm_id, grm_bin, grm_n_bin] - } - input[1] = 1 + input[0] = GCTA_MAKEGRM_DENSE_STUB.out.grm_files.map { meta, grm_files -> [meta, grm_files, 1] } """ } } @@ -196,13 +178,15 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert process.out.grm_files.get(0).get(0).id == "stub_dense.part_1_1" }, + { assert process.out.grm_files.get(0).get(0).id == "stub_dense" }, { def grm_row = process.out.grm_files.get(0) def expected_prefix = "${grm_row.get(0).id}_adj" - assert file(grm_row.get(1)).name == "${expected_prefix}.grm.id" - assert file(grm_row.get(2)).name == "${expected_prefix}.grm.bin" - assert file(grm_row.get(3)).name == "${expected_prefix}.grm.N.bin" + assert grm_row.get(1).collect { file(it).name }.sort() == [ + "${expected_prefix}.grm.N.bin", + "${expected_prefix}.grm.bin", + "${expected_prefix}.grm.id" + ] }, { assert snapshot( diff --git a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap index 3616f96e5734..db3b26af1848 100644 --- a/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap +++ b/modules/nf-core/gcta/adjustgrm/tests/main.nf.test.snap @@ -4,11 +4,13 @@ [ [ { - "id": "stub_dense.part_1_1" + "id": "stub_dense" }, - "stub_dense.part_1_1_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e", - "stub_dense.part_1_1_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", - "stub_dense.part_1_1_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "stub_dense_adj.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense_adj.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "stub_dense_adj.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], { @@ -25,18 +27,20 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T20:39:13.62183461" + "timestamp": "2026-05-15T22:57:07.586618996" }, - "homo_sapiens popgen - adjust dense GRM with 0 fallback value": { + "homo_sapiens popgen - adjust dense GRM": { "content": [ [ [ { - "id": "tiny_dense.part_1_1" + "id": "tiny_dense" }, - "tiny_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "tiny_dense.part_1_1_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", - "tiny_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + [ + "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4", + "tiny_dense_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", + "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9" + ] ] ], { @@ -53,18 +57,20 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T20:38:46.865665471" + "timestamp": "2026-05-15T22:51:12.395334799" }, - "homo_sapiens popgen - adjust dense GRM": { + "homo_sapiens popgen - adjust dense GRM with null fallback value": { "content": [ [ [ { - "id": "tiny_dense.part_1_1" + "id": "tiny_dense" }, - "tiny_dense.part_1_1_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9", - "tiny_dense.part_1_1_adj.grm.bin:md5,2daf6b143fde26dfe8e340237443ffaf", - "tiny_dense.part_1_1_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4" + [ + "tiny_dense_adj.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4", + "tiny_dense_adj.grm.bin:md5,6d16a365bd94b621963769e8314eeaa0", + "tiny_dense_adj.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9" + ] ] ], { @@ -81,6 +87,6 @@ "nf-test": "0.9.3", "nextflow": "25.10.4" }, - "timestamp": "2026-03-21T20:38:37.510503634" + "timestamp": "2026-05-15T22:52:43.193347775" } } \ No newline at end of file diff --git a/modules/nf-core/gcta/makegrm/environment.yml b/modules/nf-core/gcta/makegrm/environment.yml new file mode 100644 index 000000000000..3e22ea7b9f20 --- /dev/null +++ b/modules/nf-core/gcta/makegrm/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::gcta=1.94.1 diff --git a/modules/nf-core/gcta/makegrm/main.nf b/modules/nf-core/gcta/makegrm/main.nf new file mode 100644 index 000000000000..0bb78639e361 --- /dev/null +++ b/modules/nf-core/gcta/makegrm/main.nf @@ -0,0 +1,42 @@ +process GCTA_MAKEGRM { + tag "${meta.id}" + label 'process_medium' + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/46/46b0d05f0daa47561d87d2a9cac5e51edc2c78e26f1bbab439c688386241a274/data' + : 'community.wave.seqera.io/library/gcta:1.94.1--9bc35dc424fcf6e9'}" + + input: + tuple val(meta), path(mfile), path(bed_pgen), path(bim_pvar), path(fam_psam) + + output: + tuple val(meta), path("*.grm.*"), emit: grm_files + tuple val("${task.process}"), val("gcta"), eval("gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'"), emit: versions_gcta, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def extra_args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def genotype_files = bed_pgen instanceof List ? bed_pgen : [bed_pgen] + def genotype_extension = genotype_files[0].name.tokenize('.').last() + def multi_file_flag = genotype_extension == 'pgen' ? '--mpfile' : '--mbfile' + + """ + + gcta \\ + ${multi_file_flag} ${mfile} \\ + --make-grm \\ + --thread-num ${task.cpus} \\ + --out ${prefix} ${extra_args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.grm.id + touch ${prefix}.grm.bin + touch ${prefix}.grm.N.bin + """ +} diff --git a/modules/nf-core/gcta/makegrm/meta.yml b/modules/nf-core/gcta/makegrm/meta.yml new file mode 100644 index 000000000000..0c813dadada6 --- /dev/null +++ b/modules/nf-core/gcta/makegrm/meta.yml @@ -0,0 +1,91 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "gcta_makegrm" +description: Compute a whole dense GRM with GCTA +keywords: + - gcta + - genome-wide complex trait analysis + - grm + - genetic relationship matrix + - genetics +tools: + - "gcta": + description: "GCTA is a tool for genome-wide complex trait analysis." + homepage: "https://yanglab.westlake.edu.cn/software/gcta/" + documentation: "https://yanglab.westlake.edu.cn/software/gcta/static/gcta_doc_latest.pdf" + tool_dev_url: "https://github.com/jianyangqt/gcta" + licence: + - "GPL-3.0-only" + identifier: biotools:gcta + +input: + - - meta: + type: map + description: | + Groovy Map containing GRM sample metadata + e.g. `[ id:'gcta_grm' ]` + - mfile: + type: file + description: GCTA multi-input manifest consumed by `--mbfile` or + `--mpfile` + pattern: "*.{mbfile,mpfile,txt}" + ontologies: + - edam: "http://edamontology.org/format_2330" + - bed_pgen: + type: file + description: Collection of PLINK primary genotype files referenced by the + multi-input manifest + pattern: "*.{bed,pgen}" + ontologies: + - edam: "http://edamontology.org/format_3003" + - bim_pvar: + type: file + description: Collection of PLINK variant metadata files referenced by the + multi-input manifest + pattern: "*.{bim,pvar}" + ontologies: [] + - fam_psam: + type: file + description: Collection of PLINK sample metadata files referenced by the + multi-input manifest + pattern: "*.{fam,psam}" + ontologies: [] + +output: + grm_files: + - - meta: + type: map + description: | + Groovy Map containing GRM sample metadata + e.g. `[ id:'gcta_grm' ]` + - "*.grm.*": + type: file + description: Dense GRM sidecar files + pattern: "*.grm.{id,bin,N.bin}" + ontologies: [] + versions_gcta: + - - ${task.process}: + type: string + description: The process the version was collected from + - gcta: + type: string + description: The tool name + - "gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'": + type: eval + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - gcta: + type: string + description: The tool name + - "gcta --version | sed -En 's/^[*] version v([0-9.]*).*/\\1/p'": + type: eval + description: The command used to generate the version of the tool + +authors: + - "@lyh970817" +maintainers: + - "@lyh970817" diff --git a/modules/nf-core/gcta/makegrm/tests/main.nf.test b/modules/nf-core/gcta/makegrm/tests/main.nf.test new file mode 100644 index 000000000000..e5c63233678b --- /dev/null +++ b/modules/nf-core/gcta/makegrm/tests/main.nf.test @@ -0,0 +1,142 @@ +nextflow_process { + + name "Test Process GCTA_MAKEGRM" + script "../main.nf" + process "GCTA_MAKEGRM" + + tag "modules" + tag "modules_nfcore" + tag "gcta" + tag "gcta/makegrm" + + test("homo_sapiens popgen - plink2") { + when { + process { + """ + file('gcta_grm.mpfile').text = 'plink_simulated plink_simulated.pgen plink_simulated.psam plink_simulated.pvar\\n' + + input[0] = [ + [ id:'gcta_grm' ], + file('gcta_grm.mpfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.pgen', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.pvar', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.psam', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.grm_files.size() == 1 }, + { assert process.out.grm_files.get(0).get(0).id == 'gcta_grm' }, + { assert process.out.grm_files.get(0).get(0).keySet() == ['id'] as Set }, + { assert process.out.grm_files.get(0).get(1).size() == 3 }, + { + assert process.out.grm_files.get(0).get(1).collect { file(it).name }.toSet() == [ + 'gcta_grm.grm.id', + 'gcta_grm.grm.bin', + 'gcta_grm.grm.N.bin' + ] as Set + }, + { assert file(path(process.out.grm_files.get(0).get(1)[0]).parent.toString() + '/.command.sh').text.contains('--make-grm') }, + { assert file(path(process.out.grm_files.get(0).get(1)[0]).parent.toString() + '/.command.sh').text.contains('--mpfile') }, + { + assert snapshot( + process.out.grm_files, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() + } + ) + } + } + + test("homo_sapiens popgen - plink1") { + when { + process { + """ + file('gcta_grm.mbfile').text = 'plink_simulated\\n' + + input[0] = [ + [ id:'gcta_grm_bed' ], + file('gcta_grm.mbfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.grm_files.size() == 1 }, + { assert process.out.grm_files.get(0).get(0).id == 'gcta_grm_bed' }, + { assert process.out.grm_files.get(0).get(0).keySet() == ['id'] as Set }, + { assert process.out.grm_files.get(0).get(1).size() == 3 }, + { + assert process.out.grm_files.get(0).get(1).collect { file(it).name }.toSet() == [ + 'gcta_grm_bed.grm.id', + 'gcta_grm_bed.grm.bin', + 'gcta_grm_bed.grm.N.bin' + ] as Set + }, + { assert file(path(process.out.grm_files.get(0).get(1)[0]).parent.toString() + '/.command.sh').text.contains('--make-grm') }, + { assert file(path(process.out.grm_files.get(0).get(1)[0]).parent.toString() + '/.command.sh').text.contains('--mbfile') }, + { + assert snapshot( + process.out.grm_files, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() + } + ) + } + } + + test("homo_sapiens popgen - plink1 - stub") { + options "-stub" + + when { + process { + """ + file('gcta_grm.mbfile').text = 'plink_simulated\\n' + + input[0] = [ + [ id:'gcta_grm_bed' ], + file('gcta_grm.mbfile'), + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bed', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.bim', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/popgen/plink_simulated.fam', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/gcta/makegrm/tests/main.nf.test.snap b/modules/nf-core/gcta/makegrm/tests/main.nf.test.snap new file mode 100644 index 000000000000..f8fbe133d5a2 --- /dev/null +++ b/modules/nf-core/gcta/makegrm/tests/main.nf.test.snap @@ -0,0 +1,111 @@ +{ + "homo_sapiens popgen - plink2": { + "content": [ + [ + [ + { + "id": "gcta_grm" + }, + [ + "gcta_grm.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4", + "gcta_grm.grm.bin:md5,45f8dff14bda17d50009a21050572228", + "gcta_grm.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9" + ] + ] + ], + { + "versions_gcta": [ + [ + "GCTA_MAKEGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-05-15T21:08:43.209734458" + }, + "homo_sapiens popgen - plink1": { + "content": [ + [ + [ + { + "id": "gcta_grm_bed" + }, + [ + "gcta_grm_bed.grm.N.bin:md5,acaa43bbbf2253d392537a178ecf09a4", + "gcta_grm_bed.grm.bin:md5,45f8dff14bda17d50009a21050572228", + "gcta_grm_bed.grm.id:md5,4f9aa36c44a417ff6d7caa9841e66ad9" + ] + ] + ], + { + "versions_gcta": [ + [ + "GCTA_MAKEGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-05-15T21:09:34.058651287" + }, + "homo_sapiens popgen - plink1 - stub": { + "content": [ + { + "0": [ + [ + { + "id": "gcta_grm_bed" + }, + [ + "gcta_grm_bed.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "gcta_grm_bed.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "gcta_grm_bed.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + "GCTA_MAKEGRM", + "gcta", + "1.94.1" + ] + ], + "grm_files": [ + [ + { + "id": "gcta_grm_bed" + }, + [ + "gcta_grm_bed.grm.N.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "gcta_grm_bed.grm.bin:md5,d41d8cd98f00b204e9800998ecf8427e", + "gcta_grm_bed.grm.id:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_gcta": [ + [ + "GCTA_MAKEGRM", + "gcta", + "1.94.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-05-15T21:10:21.024687128" + } +} \ No newline at end of file diff --git a/modules/nf-core/gcta/makegrm/tests/nextflow.config b/modules/nf-core/gcta/makegrm/tests/nextflow.config new file mode 100644 index 000000000000..de31e0218829 --- /dev/null +++ b/modules/nf-core/gcta/makegrm/tests/nextflow.config @@ -0,0 +1,3 @@ +params { + modules_testdata_base_path = System.getenv("NF_MODULES_TESTDATA_BASE_PATH") ?: "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/" +}