Skip to content

Commit c48c38e

Browse files
committed
update snakemake workflow for linear elastic benchmark
1 parent b64262e commit c48c38e

19 files changed

+509
-368
lines changed
Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,101 @@
11
from pathlib import Path
22

33
files = list(Path(".").rglob("parameters_*.json"))
4-
names = [f.stem.split("_")[1] for f in files]
4+
5+
# extract the configuration from the parameter files
6+
# by reading in the json files and extracting the "configuration" value
7+
# configuration stores the appendix in the output files)"
8+
# in theory, you could make that identical so parameters_1.json with configuration "1"
9+
# would produce summary_1.json
10+
import json
11+
def get_configuration(file):
12+
with open(file, 'r') as f:
13+
data = json.load(f)
14+
# Check if "configuration" key exists, otherwise use the file name
15+
if "configuration" in data:
16+
return data["configuration"]
17+
# Fallback to using the file name if "configuration" is not present
18+
# Assuming the file name is in the format "parameters_<configuration>.json"
19+
if file.stem.startswith("parameters_"):
20+
return file.stem.split("_")[1]
21+
# If no configuration is found, raise an error
22+
raise ValueError(f"Configuration key not found for file: {file}")
23+
24+
# Create a dictionary of configurations (key is the name of the parameter file)
25+
# configurations: {Path("parameters_1.json"): "1", ...}
26+
# Reverse mapping for easy lookup by configuration name
27+
configurations = {file: get_configuration(file) for file in files if file.is_file()}
28+
configuration_to_parameter_file = {v: str(k) for k, v in configurations.items()}
529

630
rule all:
731
input:
8-
expand("summary_{name}.json", name=names),
9-
#expand("output_{name}.h5", name=names)
32+
"data/summary.json",
33+
"data/summary.h5",
1034

11-
rule generate_input_files:
35+
rule create_mesh:
1236
input:
13-
"experiment.json",
14-
"parameters_{name}.json",
37+
script = "create_mesh.py",
38+
parameters = lambda wildcards: configuration_to_parameter_file[wildcards.configuration],
1539
output:
16-
"data/input_{name}.json",
17-
"data/mesh_{name}.msh",
18-
conda: "environment.yml"
19-
shell: "python3 create_input_files.py {wildcards.name} {input}"
40+
mesh = "data/mesh_{configuration}.msh",
41+
conda: "environment_mesh.yml"
42+
shell:
43+
"""
44+
python3 {input.script} --input_parameter_file {input.parameters} --output_mesh_file {output.mesh}
45+
"""
2046

2147
rule run_simulation:
2248
input:
23-
"data/input_{name}.json",
24-
"data/mesh_{name}.msh",
49+
script = "run_simulation.py",
50+
parameters = lambda wildcards: configuration_to_parameter_file[wildcards.configuration],
51+
mesh = "data/mesh_{configuration}.msh",
2552
output:
26-
"data/output_{name}.vtk",
53+
hdf5 = "data/solution_field_data_{configuration}.h5",
54+
metrics ="data/solution_metrics_{configuration}.json",
2755
conda:
28-
"environment.yml",
29-
shell: "python3 run_simulation.py {wildcards.name} {input}"
56+
"environment_simulation.yml",
57+
shell:
58+
"""
59+
python3 {input.script} --input_parameter_file {input.parameters} --input_mesh_file {input.mesh} --output_solution_file_hdf5 {output.hdf5} --output_metrics_file {output.metrics}
60+
"""
3061

3162
rule summary:
3263
input:
33-
"data/output_{name}.vtk",
34-
"data/input_{name}.json",
35-
"data/mesh_{name}.msh",
36-
"parameters_{name}.json",
64+
parameters = expand("{param}", param=[configuration_to_parameter_file[c] for c in configurations.values()]),
65+
mesh = expand("data/mesh_{configuration}.msh", configuration=configurations.values()),
66+
metrics = expand("data/solution_metrics_{configuration}.json", configuration=configurations.values()),
67+
solution_field_data = expand("data/solution_field_data_{configuration}.h5", configuration=configurations.values()),
3768
output:
38-
"summary_{name}.json",
69+
summary_json = "data/summary.json",
70+
summary_h5 = "data/summary.h5",
71+
conda: "environment_postprocessing.yml",
3972
run:
4073
import json
41-
import pyvista
42-
summary = {}
43-
summary["name"] = wildcards.name
44-
summary["parameters"] = input[3]
45-
summary["input"] = input[1]
46-
summary["mesh"] = input[2]
47-
summary["output"] = input[0]
48-
# Load the mesh and output data
49-
max_mises_stress = 42.0
50-
from xml.etree import ElementTree as ET
51-
tree = ET.parse(input[0])
52-
root = tree.getroot()
53-
pvtu_filenames = []
54-
path = Path(input[0]).parent
55-
for dataset in root.findall(".//DataSet"):
56-
pvtu_filenames.append(path / dataset.get("file"))
57-
meshes = [pyvista.read(pvtu_filename) for pvtu_filename in pvtu_filenames]
58-
print(pvtu_filenames)
59-
for mesh in meshes:
60-
# Assuming the mesh has a 'von_mises_stress' array
61-
try:
62-
max_mises_stress = float(mesh["von_mises_stress"].max())
63-
except KeyError:
64-
print("von_mises_stress not found in mesh.")
65-
summary["max_mises_stress"] = max_mises_stress # Replace with actual computation
66-
with open(output[0], "w") as f:
67-
json.dump(summary, f, indent=4)
74+
import h5py
75+
from pathlib import Path
76+
77+
all_summaries = []
78+
79+
with h5py.File(output.summary_h5, "w") as h5f:
80+
for idx, config in enumerate(configurations.values()):
81+
mesh_path = input.mesh[idx]
82+
mesh_dataset_name = Path(mesh_path).name
83+
with open(mesh_path, "rb") as mesh_file:
84+
mesh_data = mesh_file.read()
85+
h5f.create_dataset(f"{config}/mesh", data=mesh_data)
86+
with h5py.File(input.solution_field_data[idx], "r") as src_h5f:
87+
for name, dataset in src_h5f.items():
88+
h5f.create_dataset(f"{config}/{name}", data=dataset[()])
89+
for idx, config in enumerate(configurations.values()):
90+
summary = {}
91+
summary["benchmark"] = "linear-elastic-plate-with-hole"
92+
with open(input.parameters[idx], "r") as param_file:
93+
summary["parameters"] = json.load(param_file)
94+
summary["mesh"] = f"{config}/mesh"
95+
with open(input.metrics[idx], "r") as metrics_file:
96+
summary["metrics"] = json.load(metrics_file)
97+
summary["configuration"] = config
98+
all_summaries.append(summary)
99+
100+
with open(output.summary_json, "w") as f:
101+
json.dump(all_summaries, f, indent=4)

benchmarks/linear-elastic-plate-with-hole/FEniCS/create_input_files.py

Lines changed: 0 additions & 95 deletions
This file was deleted.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import json
2+
from argparse import ArgumentParser
3+
import os
4+
5+
import gmsh
6+
from pint import UnitRegistry
7+
8+
ureg = UnitRegistry()
9+
10+
11+
def create_mesh(parameter_file, mesh_file):
12+
# Load parameters
13+
with open(parameter_file) as f:
14+
parameters = json.load(f)
15+
print(parameters)
16+
17+
# Read configuratino from parameters instead of the filename
18+
configuratino = parameters["configuration"]
19+
20+
length = (
21+
ureg.Quantity(parameters["length"]["value"], parameters["length"]["unit"])
22+
.to_base_units()
23+
.magnitude
24+
)
25+
radius = (
26+
ureg.Quantity(parameters["radius"]["value"], parameters["radius"]["unit"])
27+
.to_base_units()
28+
.magnitude
29+
)
30+
# create mesh with gmsh python api
31+
r"""
32+
4---------3
33+
| |
34+
5_ |
35+
\ |
36+
1______2
37+
38+
"""
39+
40+
gmsh.initialize()
41+
gmsh.model.add(configuratino)
42+
43+
element_size = (
44+
ureg.Quantity(
45+
parameters["element-size"]["value"], parameters["element-size"]["unit"]
46+
)
47+
.to_base_units()
48+
.magnitude
49+
)
50+
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", element_size)
51+
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", element_size)
52+
gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", 1.0)
53+
gmsh.option.setNumber("Mesh.ElementOrder", parameters["element-order"])
54+
55+
z = 0.0
56+
lc = 1.0
57+
58+
x0 = 0.0
59+
x1 = x0 + radius
60+
x2 = x0 + length
61+
y0 = 0.0
62+
y1 = y0 + radius
63+
y2 = y0 + length
64+
65+
center = gmsh.model.geo.addPoint(x0, y0, z, lc)
66+
p1 = gmsh.model.geo.addPoint(x1, y0, z, lc)
67+
p2 = gmsh.model.geo.addPoint(x2, y0, z, lc)
68+
p3 = gmsh.model.geo.addPoint(x2, y2, z, lc)
69+
p4 = gmsh.model.geo.addPoint(x0, y2, z, lc)
70+
p5 = gmsh.model.geo.addPoint(x0, y1, z, lc)
71+
72+
l1 = gmsh.model.geo.addLine(p1, p2)
73+
l2 = gmsh.model.geo.addLine(p2, p3)
74+
l3 = gmsh.model.geo.addLine(p3, p4)
75+
l4 = gmsh.model.geo.addLine(p4, p5)
76+
l5 = gmsh.model.geo.addCircleArc(p5, center, p1)
77+
78+
curve = gmsh.model.geo.addCurveLoop([l1, l2, l3, l4, l5])
79+
plane = gmsh.model.geo.addPlaneSurface([curve])
80+
gmsh.model.geo.synchronize()
81+
gmsh.model.geo.removeAllDuplicates()
82+
gmsh.model.addPhysicalGroup(2, [plane], 1, name="surface")
83+
gmsh.model.addPhysicalGroup(1, [l4], 1, name="boundary_left")
84+
gmsh.model.addPhysicalGroup(1, [l1], 2, name="boundary_bottom")
85+
gmsh.model.addPhysicalGroup(1, [l2], 3, name="boundary_right")
86+
gmsh.model.addPhysicalGroup(1, [l3], 4, name="boundary_top")
87+
88+
gmsh.model.mesh.generate(2)
89+
gmsh.write(mesh_file)
90+
gmsh.finalize()
91+
92+
93+
if __name__ == "__main__":
94+
PARSER = ArgumentParser(
95+
description="Create input files and mesh for FEniCS simulation"
96+
)
97+
PARSER.add_argument(
98+
"--input_parameter_file", required=True, help="JSON file containing simulation parameters"
99+
)
100+
PARSER.add_argument(
101+
"--output_mesh_file", required=True, help="Output path for the generated mesh (.msh)"
102+
)
103+
ARGS = vars(PARSER.parse_args())
104+
105+
create_mesh(ARGS["input_parameter_file"], ARGS["output_mesh_file"])
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: model-validation
22
channels:
33
- conda-forge
4+
5+
channel_priority: strict
6+
47
dependencies:
5-
- python>=3.10
6-
- fenics-dolfinx
7-
- libadios2=2.10.1
8-
- petsc4py
9-
- python-gmsh
10-
- pint
8+
- python>=3.12
9+
- snakemake
10+
- h5py
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: model-validation
2+
channels:
3+
- conda-forge
4+
5+
channel_priority: strict
6+
7+
dependencies:
8+
- python=3.12
9+
- pint
10+
- python-gmsh
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: postprocessing
2+
channels:
3+
- conda-forge
4+
5+
channel_priority: strict
6+
7+
dependencies:
8+
- python=3.12
9+
- pint
10+
- pyvista

0 commit comments

Comments
 (0)