-
Notifications
You must be signed in to change notification settings - Fork 4
Run DuMuX simulation inside container via Snakemake #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ab25875
9d2e868
91fd400
ef52760
edc0793
ad3298d
443a91a
eb7dc63
3fc7ecb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| 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 |
| 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} | ||
| """ |
| 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", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 |
||
| 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: | ||
joergfunger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
srosenbu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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" | ||
| } | ||
| } |
| 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() | ||
| ) |
| 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" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.