Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/run-dumux-benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: DuMux-CI
on:
push:

pull_request:
branches: [ main ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Runs the workflow once per day at 3:15am
schedule:
- cron: '3 16 * * *'

env:
CACHE_NUMBER: 1 # increase to reset cache manually

jobs:
tests:
runs-on: ubuntu-latest

steps:
- name: checkout repo content
uses: actions/checkout@v2

- name: Setup Mambaforge
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-version: latest
activate-environment: model-validation
use-mamba: true

- name: Set strict channel priority
run: conda config --set channel_priority strict

- name: Setup Apptainer
uses: eWaterCycle/setup-apptainer@v2
with:
apptainer-version: 1.4.5

- name: Update environment
run: mamba env update -n model-validation -f environment_benchmarks.yml

- name: generate-rc-config
shell: bash -l {0}
run: |
cd $GITHUB_WORKSPACE/benchmarks/rotating-cylinders
python3 generate_rc_config.py

- name: run-rotating-cylinders-snakemake
shell: bash -l {0}
run: |
cd $GITHUB_WORKSPACE/benchmarks/rotating-cylinders
# Note: --use-singularity is aliased to --use-apptainer in modern Snakemake
snakemake --use-apptainer --cores all --resources serial_run=1 --apptainer-args "--bind $(pwd):/dumux/shared"
14 changes: 12 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
.snakemake
site
site

# macOS system files
.DS_Store

# Snakemake hidden folders
.snakefile/
.snakemake/

# Sentinel / temporary files
*.done
.simulation_done
63 changes: 63 additions & 0 deletions benchmarks/rotating-cylinders/Snakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# benchmarks/rotating-cylinders/Snakefile
import json
import os

if not os.path.exists("rotating-cylinders_config.json"):
os.system("python3 generate_rc_config.py")

configfile: "rotating-cylinders_config.json"

# Variables
tools = config["tools"]
configs = config["configurations"]
benchmark = config["benchmark"]
benchmark_uri = config["benchmark_uri"]
result_dir = f"snakemake_results/{benchmark}"
shared_dir = os.getcwd()

rule all:
input:
expand(f"{result_dir}/{{tool}}/summary.json", tool=tools)

for tool in tools:
include: f"{tool}/Snakefile"

rule summary:
input:
script = "../common/summarize_results.py",

parameters=lambda wc: expand(
f"{shared_dir}/{wc.tool}/grid_files/grid_{{conf}}.json",
conf=configs
),

mesh=lambda wc: expand(
f"{shared_dir}/{wc.tool}/grid_files/grid_{{conf}}.json",
conf=configs
),

metrics=lambda wc: expand(
f"{result_dir}/{wc.tool}/solution_metrics_{{conf}}.json",
conf=configs
),

solution_field_data=lambda wc: expand(
f"{result_dir}/{wc.tool}/solution_field_data_{{conf}}.zip",
conf=configs
)

output:
summary_json=f"{result_dir}/{{tool}}/summary.json"

shell:
"""
python3 {input.script} \
--input_configuration {configs} \
--input_parameter_file {input.parameters} \
--input_mesh_file {input.mesh} \
--input_solution_metrics {input.metrics} \
--input_solution_field_data {input.solution_field_data} \
--input_benchmark {benchmark} \
--input_benchmark_uri {benchmark_uri} \
--output_summary_json {output.summary_json}
"""
60 changes: 60 additions & 0 deletions benchmarks/rotating-cylinders/dumux/Snakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# benchmarks/rotating-cylinders/dumux/Snakefile
import os

# Setup and Config
tool = "dumux"
container_image = config["container_image"]
dumux_dir = f"{shared_dir}/dumux"

# Rule 1: Input generation
rule generate_dumux_inputs:
input:
grid_t = f"{dumux_dir}/grid_files/grid_template.json",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have thought that parameter.json should be an input to this rule? I see that you regenerate the parameters in input_gen_script.py but I think that is not the intention.

Copy link
Copy Markdown
Collaborator Author

@Sarbani-Roy Sarbani-Roy Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script merges the grid file with other parameters into a single input file, as Dumux requires one parameter file containing all inputs (including grids). As discussed previously, to ensure consistent metric representation across benchmarks, we need to use the summarize_results.py script:
https://github.com/BAMresearch/NFDI4IngModelValidationPlatform/blob/main/benchmarks/common/summarize_results.py

This script requires a separate grid file as input. Therefore, we specified grids in a separate file and merged it with other parameters to create a Dumux-compatible input file. If the grid file argument in summarize_results.py is optional, I could use a single parameter file with all inputs and remove the grid input generation block from the corresponding Snakefile. Or instead of Snakefile I can do this in the workflow file. Which one do you suggest? Or, do you have any other suggestions for handling this?

dumux_t = f"{dumux_dir}/dumux_input_files/dumux_config.json",
input_gen_script = f"{dumux_dir}/dumux_input_gen.py"
output:
params = expand(f"{dumux_dir}/dumux_input_files/params_{{conf}}.json", conf=configs),
grids = expand(f"{dumux_dir}/grid_files/grid_{{conf}}.json", conf=configs)
shell:
"python3 {input.input_gen_script} --grid_template {input.grid_t} --dumux_template {input.dumux_t}"

# Rule 2: Simulation
rule run_dumux_simulation:
input:
# Lambda prevents KeyError before the JSON is generated
params = lambda wildcards: f"{dumux_dir}/dumux_input_files/{config['configuration_to_parameter_file'][wildcards.configuration]}"
output:
vtu_files = [
f"{dumux_dir}/test_rotatingcylinders_{{configuration}}-00000.vtu",
f"{dumux_dir}/test_rotatingcylinders_{{configuration}}-00001.vtu"
]
resources:
serial_run=1
singularity:
f"docker://{container_image}"
shell:
"""
set -euo pipefail
cd /dumux/rotating-cylinders/build-cmake/test/freeflow/navierstokes/rotatingcylinders
./test_ff_navierstokes_rotatingcylinders JsonParameterFile={input.params}
"""

# Rule 3: Post-processing
rule postprocess_dumux:
input:
sim_data = [
f"{dumux_dir}/test_rotatingcylinders_{{configuration}}-00000.vtu",
f"{dumux_dir}/test_rotatingcylinders_{{configuration}}-00001.vtu"
],
postprocess_script = f"{dumux_dir}/run_dumux_postprocessing.py"
output:
metrics = f"{result_dir}/{tool}/solution_metrics_{{configuration}}.json",
fields = f"{result_dir}/{tool}/solution_field_data_{{configuration}}.zip"
shell:
"""
python3 {input.postprocess_script} \
--input_dumux_output_dir {dumux_dir} \
--input_configuration {wildcards.configuration} \
--output_solution_file_zip {output.fields} \
--output_metrics_file {output.metrics}
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"Problem": {
"Name": "test_rotatingcylinders",
"Omega1": 1e2,
"Omega2": 0,
"EnableGravity": false,
"EnableInertiaTerms": true
},
"FreeFlow": {
"EnableUnsymmetrizedVelocityGradient": true
},
"Flux": {
"UpwindWeight": 0.5
},
"Component": {
"LiquidDensity": 1,
"LiquidDynamicViscosity": 1
},
"Mass": {
"Assembly": {
"NumericDifference": {
"PriVarMagnitude": "1e-2",
"BaseEpsilon": 0.01
}
}
},
"Momentum": {
"Assembly": {
"NumericDifference": {
"PriVarMagnitude": "0.2 0.2",
"BaseEpsilon": 0.01
}
}
},
"LinearSolver": {
"MaxIterations": 500,
"ResidualReduction": "1e-10",
"SymmetrizeDirichlet": true,
"DirectSolverForVelocity": false,
"GMResRestart": 500,
"Type": "gmres",
"Verbosity": 1,
"Preconditioner": {
"Mode": "Triangular",
"Iterations": 5,
"AmgSmootherIterations": 2,
"AmgDefaultAggregationDimension": 2,
"AmgMinAggregateSize": 2,
"AmgMaxAggregateSize": 2,
"AmgAdditive": false,
"AmgGamma": 1,
"AmgCriterionSymmetric": true
}
},
"Newton": {
"MinSteps": 1,
"EnableAbsoluteResidualCriterion": true,
"MaxAbsoluteResidual": "4e-6"
}
}
86 changes: 86 additions & 0 deletions benchmarks/rotating-cylinders/dumux/dumux_input_gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import json
import argparse
from pathlib import Path

def generate_grid_files(grid_template_path, grid_dir, base_cells0, base_cells1, num_files):
grid_dir.mkdir(parents=True, exist_ok=True)

if not grid_template_path.exists():
print(f"Error: {grid_template_path} not found.")
return []

with open(grid_template_path, "r") as f:
grid_template = json.load(f)

generated_configs = []

for i in range(num_files):
scale = 2 ** i
c0 = base_cells0 * scale
c1 = base_cells1 * scale
config_id = f"{c0}_{c1}"

current_grid = json.loads(json.dumps(grid_template))
current_grid["Grid"]["Cells0"] = f"{c0} {c0}"
current_grid["Grid"]["Cells1"] = c1

grid_file_path = grid_dir / f"grid_{config_id}.json"
with open(grid_file_path, "w") as f:
json.dump(current_grid, f, indent=4)

generated_configs.append((config_id, current_grid))
print(f"Generated Grid JSON: {grid_file_path}")

return generated_configs


def write_dumux_inputs_json(grid_template, dumux_template, grid_out, input_out):
problem_name_base = "/dumux/shared/dumux/test_rotatingcylinders"
base_cells0, base_cells1 = 10, 80
num_files = 3

if not dumux_template.exists():
print(f"Error: dumux template not found at {dumux_template}")
return

with open(dumux_template, "r") as f:
dumux_config = json.load(f)

grid_configs = generate_grid_files(
grid_template, grid_out, base_cells0, base_cells1, num_files
)

input_out.mkdir(parents=True, exist_ok=True)

for config_id, grid_data in grid_configs:
full_config = json.loads(json.dumps(dumux_config))

full_config["Problem"]["Name"] = f"{problem_name_base}_{config_id}"
full_config.update(grid_data)

output_file = input_out / f"params_{config_id}.json"

with open(output_file, "w") as f:
json.dump(full_config, f, indent=4)

print(f"Generated JSON Input: {output_file}")


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate DuMuX JSON input files from templates."
)

parser.add_argument("--grid_template", type=str, required=True)
parser.add_argument("--dumux_template", type=str, required=True)
parser.add_argument("--grid_dir", type=str, default="./dumux/grid_files")
parser.add_argument("--input_dir", type=str, default="./dumux/dumux_input_files")

args = parser.parse_args()

write_dumux_inputs_json(
grid_template=Path(args.grid_template).resolve(),
dumux_template=Path(args.dumux_template).resolve(),
grid_out=Path(args.grid_dir).resolve(),
input_out=Path(args.input_dir).resolve()
)
10 changes: 10 additions & 0 deletions benchmarks/rotating-cylinders/dumux/grid_files/grid_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Grid": {
"Cells0": "{cells0_x} {cells0_y}",
"Cells1": "{cells1}",
"Grading0": "1.1 -1.1",
"Grading1": "1.0",
"Radial0": "1.0 1.5 2.0",
"Angular1": "0.0 360.0"
}
}
Loading
Loading