Skip to content

Commit 91fd400

Browse files
committed
dumux simulation can run via snakemake rule
1 parent 9d2e868 commit 91fd400

9 files changed

Lines changed: 388 additions & 64 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# benchmarks/rotating-cylinders/Snakefile
2+
3+
import json
4+
import os
5+
6+
# --------------------------
7+
# Load workflow configuration
8+
# --------------------------
9+
with open("rotating-cylinders_config.json") as f:
10+
config = json.load(f)
11+
12+
tools = config["tools"]
13+
benchmark = config["benchmark"]
14+
benchmark_uri = config["benchmark_uri"]
15+
configurations = config["configurations"]
16+
config_to_param = config["configuration_to_parameter_file"]
17+
config_to_vtu_files = config.get("configuration_to_solution_vtu_files", {})
18+
19+
# --------------------------
20+
# Helper functions
21+
# --------------------------
22+
def parameter_file(cfg):
23+
return config_to_param[cfg]
24+
25+
def vtu_files(cfg):
26+
return config_to_vtu_files.get(cfg, [])
27+
28+
def summary_file(tool_name):
29+
return f"{tool_name}_summary.json"
30+
31+
# --------------------------
32+
# Include tool Snakefiles
33+
# --------------------------
34+
for tool in tools:
35+
include: f"{tool}/Snakefile"
36+
37+
# --------------------------
38+
# Global result directory
39+
# --------------------------
40+
result_dir = f"snakemake_results/rotating-cylinders"
41+
42+
# --------------------------
43+
# Rule: Aggregate all outputs
44+
# --------------------------
45+
rule all:
46+
input:
47+
expand(f"{result_dir}/dumux/solution_metrics_{{configuration}}.json", configuration=configurations),
48+
expand(f"{result_dir}/dumux/solution_field_data_{{configuration}}.zip", configuration=configurations)
49+
50+
# --------------------------
51+
# Rule: Create summary
52+
# --------------------------
53+
rule create_summary:
54+
input:
55+
parameter_files=[parameter_file(cfg) for cfg in configurations],
56+
solution_vtu_files=sum([vtu_files(cfg) for cfg in configurations], []) # flatten list of lists
57+
output:
58+
summary=summary_file("dumux")
59+
params:
60+
configs=configurations
61+
shell:
62+
"""
63+
python create_dumux_summary.py \
64+
--input_configuration {params.configs} \
65+
--input_parameter_file {input.parameter_files} \
66+
--input_solution_vtu {input.solution_vtu_files} \
67+
--input_benchmark "{benchmark}" \
68+
--input_benchmark_uri "{benchmark_uri}" \
69+
--output_summary_json {output.summary}
70+
"""
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"benchmark": "rotating-cylinders",
3+
"benchmark_uri": "https://www.openfoam.com/documentation/guides/latest/doc/verification-validation-rotating-cylinders-2d.html",
4+
"tools": ["dumux"],
5+
"configurations": [],
6+
"configuration_to_parameter_file": {},
7+
"configuration_to_solution_vtu_files": {}
8+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import json
2+
import argparse
3+
import meshio
4+
5+
def create_dumux_summary(configurations, parameter_files, solution_vtu_files, benchmark, benchmark_uri, summary_json):
6+
summaries = []
7+
for cfg, param_file, vtu_file in zip(configurations, parameter_files, solution_vtu_files):
8+
summary = {
9+
"benchmark": benchmark,
10+
"benchmark_uri": benchmark_uri,
11+
"configuration": cfg
12+
}
13+
# Load parameters
14+
with open(param_file) as f:
15+
summary["parameters"] = json.load(f)
16+
17+
summary["solution_vtu"] = vtu_file
18+
19+
# Extract field summaries
20+
vtu = meshio.read(vtu_file)
21+
field_summaries = {}
22+
for key, data in vtu.point_data.items():
23+
field_summaries[key] = {
24+
"min": float(data.min()),
25+
"max": float(data.max()),
26+
"mean": float(data.mean())
27+
}
28+
summary["solution_fields_summary"] = field_summaries
29+
30+
summaries.append(summary)
31+
32+
with open(summary_json, "w") as f:
33+
json.dump(summaries, f, indent=4)
34+
35+
36+
if __name__ == "__main__":
37+
parser = argparse.ArgumentParser()
38+
parser.add_argument("--input_configuration", nargs="+", required=True)
39+
parser.add_argument("--input_parameter_file", nargs="+", required=True)
40+
parser.add_argument("--input_solution_vtu", nargs="+", required=True)
41+
parser.add_argument("--input_benchmark", required=True)
42+
parser.add_argument("--input_benchmark_uri", required=True)
43+
parser.add_argument("--output_summary_json", required=True)
44+
args = parser.parse_args()
45+
46+
create_dumux_summary(
47+
args.input_configuration,
48+
args.input_parameter_file,
49+
args.input_solution_vtu,
50+
args.input_benchmark,
51+
args.input_benchmark_uri,
52+
args.output_summary_json
53+
)
Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
1-
# Snakefile — rotating cylinders benchmark
2-
31
import os
4-
import glob
52

3+
# 1. Setup and Config
64
tool = "dumux"
5+
result_dir = f"snakemake_results/rotating-cylinders/{tool}"
6+
configurations = config["configurations"]
7+
configuration_to_parameter_file = config["configuration_to_parameter_file"]
8+
vtu_mapping = config["configuration_to_solution_vtu_files"]
79

8-
# Directory where simulation writes VTU files
9-
output_dir = "."
10+
container_image = "git.iws.uni-stuttgart.de:4567/benchmarks/rotating-cylinders:3.0"
11+
shared_dir = os.getcwd()
12+
# dumux_input = f"{shared_dir}/{tool}/input_files"
13+
dumux_dir = f"{shared_dir}/{tool}"
1014

11-
# Dynamically find all VTU files starting with "test_rotatingcylinders"
12-
vtu_files = glob.glob(os.path.join(output_dir, "test_rotatingcylinders*.vtu"))
15+
# Rule 1: Only runs the simulation, outputs the VTU files
16+
rule run_dumux_simulation:
17+
input:
18+
params=lambda wildcards: f"{dumux_dir}/input_files/{configuration_to_parameter_file[wildcards.configuration]}"
19+
output:
20+
# Use a list of the actual files you expect
21+
vtu_files = [
22+
f"{tool}/test_rotatingcylinders_{{configuration}}-00000.vtu",
23+
f"{tool}/test_rotatingcylinders_{{configuration}}-00001.vtu"
24+
]
25+
resources:
26+
serial_run=1
27+
singularity:
28+
f"docker://{container_image}"
29+
shell:
30+
"""
31+
set -euo pipefail
32+
cd /dumux/rotating-cylinders/build-cmake/test/freeflow/navierstokes/rotatingcylinders
33+
./test_ff_navierstokes_rotatingcylinders {input.params} -IO.OutputPath {dumux_dir}
34+
"""
1335

14-
rule run_rotating_cylinders_simulation:
36+
# Rule 2: Only runs post-processing, depends on the VTU files
37+
rule postprocess_dumux:
1538
input:
16-
script = "docker_rotatingCylinders.sh"
39+
sim_data = [
40+
f"{tool}/test_rotatingcylinders_{{configuration}}-00000.vtu",
41+
f"{tool}/test_rotatingcylinders_{{configuration}}-00001.vtu"
42+
],
43+
postprocess_script=f"{tool}/run_dumux_postprocessing.py"
1744
output:
18-
# We can use a sentinel file since the container produces multiple outputs
19-
os.path.join(output_dir, ".simulation_done")
20-
conda:
21-
"environment.yaml",
45+
metrics=f"{result_dir}/solution_metrics_{{configuration}}.json",
46+
fields=f"{result_dir}/solution_field_data_{{configuration}}.zip"
2247
shell:
2348
"""
24-
bash {input.script} open
25-
touch {output}
49+
python3 {input.postprocess_script} \
50+
--input_dumux_output_dir {dumux_dir} \
51+
--input_configuration {wildcards.configuration} \
52+
--output_solution_file_zip {output.fields} \
53+
--output_metrics_file {output.metrics}
2654
"""

benchmarks/rotating-cylinders/dumux/docker_rotatingCylinders.sh

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import json
2+
import zipfile
3+
from argparse import ArgumentParser
4+
from pathlib import Path
5+
6+
import meshio
7+
import numpy as np
8+
9+
10+
def run_dumux_postprocessing(
11+
dumux_output_dir: str,
12+
configuration: str,
13+
metrics_file: str,
14+
solution_file_zip: str,
15+
) -> None:
16+
dumux_output_dir = Path(dumux_output_dir)
17+
18+
# ---- read all VTU / PVTU files ----
19+
vtk_files = list(dumux_output_dir.glob("*.vtu")) + list(
20+
dumux_output_dir.glob("*.pvtu")
21+
)
22+
if not vtk_files:
23+
raise RuntimeError("No VTU/PVTU files found in DuMuX output directory")
24+
25+
# Read first file for metrics (extend if needed)
26+
mesh = meshio.read(vtk_files[0])
27+
28+
metrics = {}
29+
30+
# ---- example metrics (adapt to benchmark definition) ----
31+
if "p" in mesh.cell_data_dict:
32+
p = mesh.cell_data_dict["p"]
33+
# flatten possible block structure
34+
p_vals = np.concatenate([np.asarray(v).ravel() for v in p.values()])
35+
metrics["min_pressure"] = float(np.min(p_vals))
36+
metrics["max_pressure"] = float(np.max(p_vals))
37+
metrics["mean_pressure"] = float(np.mean(p_vals))
38+
39+
if "velocity_liq (m/s)" in mesh.cell_data_dict:
40+
v = mesh.cell_data_dict["velocity_liq (m/s)"]
41+
v_vals = np.concatenate([np.asarray(vv) for vv in v.values()])
42+
speed = np.linalg.norm(v_vals, axis=1)
43+
metrics["max_velocity_magnitude"] = float(np.max(speed))
44+
metrics["mean_velocity_magnitude"] = float(np.mean(speed))
45+
46+
# ---- write metrics JSON ----
47+
with open(metrics_file, "w") as f:
48+
json.dump(metrics, f, indent=4)
49+
50+
# ---- zip solution field data ----
51+
with zipfile.ZipFile(solution_file_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
52+
for file in vtk_files:
53+
zipf.write(file, arcname=file.name)
54+
55+
# ---- cleanup: delete original VTU/PVTU files ----
56+
for ext in ["*.vtu", "*.pvtu", "*.pvd"]:
57+
for file in dumux_output_dir.glob(ext):
58+
file.unlink()
59+
60+
61+
if __name__ == "__main__":
62+
parser = ArgumentParser(
63+
description="Post-process DuMuX results into benchmark artifacts"
64+
)
65+
parser.add_argument(
66+
"--input_dumux_output_dir",
67+
required=True,
68+
help="Directory containing DuMuX VTU/PVTU files",
69+
)
70+
parser.add_argument(
71+
"--input_configuration",
72+
required=True,
73+
help="Configuration name",
74+
)
75+
parser.add_argument(
76+
"--output_metrics_file",
77+
required=True,
78+
help="Path to solution_metrics_{configuration}.json",
79+
)
80+
parser.add_argument(
81+
"--output_solution_file_zip",
82+
required=True,
83+
help="Path to solution_field_data_{configuration}.zip",
84+
)
85+
86+
args = parser.parse_args()
87+
88+
run_dumux_postprocessing(
89+
args.input_dumux_output_dir,
90+
args.input_configuration,
91+
args.output_metrics_file,
92+
args.output_solution_file_zip,
93+
)

benchmarks/rotating-cylinders/dumux/environment.yaml renamed to benchmarks/rotating-cylinders/environment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ dependencies:
88
- pip
99
- pip:
1010
- snakemake
11+
- meshio

0 commit comments

Comments
 (0)