Skip to content

Commit 5e9e614

Browse files
committed
outsource fenics into a subworkflow
1 parent 4208737 commit 5e9e614

File tree

12 files changed

+732
-85
lines changed

12 files changed

+732
-85
lines changed

.github/workflows/run-benchmark.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
shell: bash -l {0}
4040
run: |
4141
cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/FEniCS/
42+
python generate_config.py
4243
snakemake --use-conda --force --cores 'all'
4344
snakemake --use-conda --force --cores 'all' --reporter metadata4ing
4445

benchmarks/linear-elastic-plate-with-hole/FEniCS/Snakefile

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,12 @@
1-
from pathlib import Path
2-
from os.path import join
3-
4-
files = list(Path(".").glob("parameters_*.json"))
5-
6-
# extract the configuration from the parameter files
7-
# by reading in the json files and extracting the "configuration" value
8-
# configuration stores the appendix in the output files)"
9-
# in theory, you could make that identical so parameters_1.json with configuration "1"
10-
# would produce summary_1.json
111
import json
12-
def get_configuration(file):
13-
with open(file, 'r') as f:
14-
data = json.load(f)
15-
# Check if "configuration" key exists, otherwise use the file name
16-
if "configuration" in data:
17-
return data["configuration"]
18-
# Fallback to using the file name if "configuration" is not present
19-
# Assuming the file name is in the format "parameters_<configuration>.json"
20-
if file.stem.startswith("parameters_"):
21-
return file.stem.split("_")[1]
22-
# If no configuration is found, raise an error
23-
raise ValueError(f"Configuration key not found for file: {file}")
24-
25-
# Create a dictionary of configurations (key is the name of the parameter file)
26-
# configurations: {Path("parameters_1.json"): "1", ...}
27-
configurations = {file: get_configuration(file) for file in files if file.is_file()}
2+
configfile: "workflow_config.json"
283

29-
# Check for duplicate configuration values (the configurations should be unique)
30-
config_values = list(configurations.values())
31-
duplicates = set([x for x in config_values if config_values.count(x) > 1])
32-
if duplicates:
33-
raise ValueError(f"Duplicate configuration values found in parameter files: {', '.join(duplicates)}")
4+
result_dir = config["result_dir"]
5+
configuration_to_parameter_file = config["configuration_to_parameter_file"]
6+
configurations = config["configurations"]
7+
tools = config["tools"]
8+
benchmark = config["benchmark"]
349

35-
# Reverse mapping for easy lookup by configuration name
36-
configuration_to_parameter_file = {v: str(k) for k, v in configurations.items()}
37-
38-
tools = ["fenics"]
39-
benchmark = "linear-elastic-plate-with-hole"
40-
# results are stored in snakemake_results/linear-elastic-plate-with-hole/fenics
41-
result_dir = join("snakemake_results", benchmark)
4210

4311
rule all:
4412
input:
@@ -59,35 +27,30 @@ rule create_mesh:
5927
python3 {input.script} --input_parameter_file {input.parameters} --output_mesh_file {output.mesh}
6028
"""
6129

62-
rule run_simulation:
63-
input:
64-
script = "{tool}/run_simulation.py",
65-
parameters = lambda wildcards: configuration_to_parameter_file[wildcards.configuration],
66-
mesh = f"{result_dir}/mesh/mesh_{{configuration}}.msh",
67-
output:
68-
zip = f"{result_dir}/{{tool}}/solution_field_data_{{configuration}}.zip",
69-
metrics = f"{result_dir}/{{tool}}/solution_metrics_{{configuration}}.json",
70-
conda:
71-
"{tool}/environment_simulation.yml",
72-
shell:
73-
"""
74-
python3 {input.script} --input_parameter_file {input.parameters} --input_mesh_file {input.mesh} --output_solution_file_zip {output.zip} --output_metrics_file {output.metrics}
75-
"""
30+
# Include tool-specific rules
31+
# The should have at least the mesh file and the parameters as input
32+
# and output for each configuration a
33+
# solution_metrics_{configuration}.json and
34+
# and solution_field_data_{configuration}.zip whee all the visualization files are stored
35+
# (e.g. vtk)
36+
for tool in tools:
37+
include: f"{tool}/Snakefile"
38+
7639

7740
rule summary:
7841
input:
7942
# the summary is performed for all configurations saved into a single file
8043
# (snakemake_results/linear-elastic-plate-with-hole/fenics/summary.json)
81-
parameters = expand("{param}", param=[configuration_to_parameter_file[c] for c in configurations.values()]),
82-
mesh = expand(f"{result_dir}/mesh/mesh_{{configuration}}.msh", configuration=configurations.values()),
44+
parameters = expand("{param}", param=[configuration_to_parameter_file[c] for c in configurations]),
45+
mesh = expand(f"{result_dir}/mesh/mesh_{{configuration}}.msh", configuration=configurations),
8346
metrics = lambda wildcards: expand(
8447
f"{result_dir}/{{tool}}/solution_metrics_{{configuration}}.json",
85-
tool=[wildcards.tool], configuration=configurations.values()
48+
tool=[wildcards.tool], configuration=configurations
8649
),
8750
solution_field_data = lambda wildcards: expand(
8851
f"{result_dir}/{{tool}}/solution_field_data_{{configuration}}.zip",
89-
tool=[wildcards.tool], configuration=configurations.values()
90-
),
52+
tool=[wildcards.tool], configuration=configurations
53+
),
9154
output:
9255
summary_json = f"{result_dir}/{{tool}}/summary.json",
9356
conda: "environment_postprocessing.yml",
@@ -96,7 +59,7 @@ rule summary:
9659
from pathlib import Path
9760

9861
all_summaries = []
99-
for idx, config in enumerate(configurations.values()):
62+
for idx, config in enumerate(configurations):
10063
summary = {}
10164
summary["benchmark"] = benchmark
10265
with open(input.parameters[idx], "r") as param_file:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import json
2+
3+
result_dir = config["result_dir"]
4+
configuration_to_parameter_file = config["configuration_to_parameter_file"]
5+
configurations = config["configurations"]
6+
tool = "fenics"
7+
8+
9+
rule run_simulation:
10+
input:
11+
script = "{tool}/run_simulation.py",
12+
parameters = lambda wildcards: configuration_to_parameter_file[wildcards.configuration],
13+
mesh = f"{result_dir}/mesh/mesh_{{configuration}}.msh",
14+
output:
15+
zip = f"{result_dir}/{{tool}}/solution_field_data_{{configuration}}.zip",
16+
metrics = f"{result_dir}/{{tool}}/solution_metrics_{{configuration}}.json",
17+
conda:
18+
"environment_simulation.yml",
19+
shell:
20+
"""
21+
python3 {input.script} --input_parameter_file {input.parameters} --input_mesh_file {input.mesh} --output_solution_file_zip {output.zip} --output_metrics_file {output.metrics}
22+
"""

benchmarks/linear-elastic-plate-with-hole/FEniCS/fenics/environment_simulation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dependencies:
1212
- petsc4py
1313
- pint
1414
- python-gmsh
15+
- sympy

benchmarks/linear-elastic-plate-with-hole/FEniCS/fenics/run_simulation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def run_simulation(
7676
.magnitude
7777
)
7878

79-
solution = PlateWithHoleSolution(
79+
analytical_solution = PlateWithHoleSolution(
8080
E=E,
8181
nu=nu,
8282
radius=radius,
@@ -118,7 +118,7 @@ def as_tensor(v):
118118

119119
u = df.fem.Function(V, name="u")
120120
u_prescribed = df.fem.Function(V, name="u_prescribed")
121-
u_prescribed.interpolate(lambda x: solution.displacement(x))
121+
u_prescribed.interpolate(lambda x: analytical_solution.displacement(x))
122122
u_prescribed.x.scatter_forward()
123123

124124
u_ = ufl.TestFunction(V)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# generate_config.py
2+
import json
3+
4+
from pathlib import Path
5+
from os.path import join
6+
7+
files = list(Path(".").glob("parameters_*.json"))
8+
9+
# extract the configuration from the parameter files
10+
# by reading in the json files and extracting the "configuration" value
11+
# configuration stores the appendix in the output files)"
12+
# in theory, you could make that identical so parameters_1.json with configuration "1"
13+
# would produce summary_1.json
14+
import json
15+
def get_configuration(file):
16+
with open(file, 'r') as f:
17+
data = json.load(f)
18+
# Check if "configuration" key exists, otherwise use the file name
19+
if "configuration" in data:
20+
return data["configuration"]
21+
# Fallback to using the file name if "configuration" is not present
22+
# Assuming the file name is in the format "parameters_<configuration>.json"
23+
if file.stem.startswith("parameters_"):
24+
return file.stem.split("_")[1]
25+
# If no configuration is found, raise an error
26+
raise ValueError(f"Configuration key not found for file: {file}")
27+
28+
# Create a dictionary of configurations (key is the name of the parameter file)
29+
# configurations: {Path("parameters_1.json"): "1", ...}
30+
configurations = {file: get_configuration(file) for file in files if file.is_file()}
31+
32+
# Check for duplicate configuration values (the configurations should be unique)
33+
config_values = list(configurations.values())
34+
duplicates = set([x for x in config_values if config_values.count(x) > 1])
35+
if duplicates:
36+
raise ValueError(f"Duplicate configuration values found in parameter files: {', '.join(duplicates)}")
37+
38+
# Reverse mapping for easy lookup by configuration name
39+
configuration_to_parameter_file = {v: str(k) for k, v in configurations.items()}
40+
41+
benchmark = "linear-elastic-plate-with-hole"
42+
# results are stored in snakemake_results/linear-elastic-plate-with-hole/fenics
43+
result_dir = join("snakemake_results", benchmark)
44+
45+
workflow_config = {
46+
"result_dir": result_dir,
47+
"configuration_to_parameter_file": configuration_to_parameter_file,
48+
"configurations": list(configurations.values()),
49+
"tools": ["fenics"],
50+
"benchmark": "linear-elastic-plate-with-hole"
51+
}
52+
with open("workflow_config.json", "w") as f:
53+
json.dump(workflow_config, f, indent=4)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import print_function, absolute_import, division #makes KratosMultiphysics backward compatible with python 2.6 and 2.7
2+
3+
import KratosMultiphysics
4+
from KratosMultiphysics.StructuralMechanicsApplication.structural_mechanics_analysis import StructuralMechanicsAnalysis
5+
import sys
6+
"""
7+
For user-scripting it is intended that a new class is derived
8+
from StructuralMechanicsAnalysis to do modifications
9+
"""
10+
parameters = sys.argv[1]
11+
12+
if __name__ == "__main__":
13+
14+
with open(parameters,'r') as parameter_file:
15+
parameters = KratosMultiphysics.Parameters(parameter_file.read())
16+
17+
model = KratosMultiphysics.Model()
18+
simulation = StructuralMechanicsAnalysis(model,parameters)
19+
simulation.Run()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"properties": [
3+
{
4+
"model_part_name": "Structure",
5+
"properties_id": 1,
6+
"Material": {
7+
"constitutive_law": {
8+
"name": "LinearElasticPlaneStress2DLaw"
9+
},
10+
"Variables": {
11+
"YOUNG_MODULUS": "{{YOUNG_MODULUS}}",
12+
"POISSON_RATIO": "{{POISSON_RATIO}}"
13+
},
14+
"Tables": {}
15+
}
16+
}
17+
]
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: model-validation
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- python=3.10
6+
- python-gmsh
7+
- pint
8+
- meshio
9+
- pip
10+
- pip:
11+
- KratosMultiphysics-all

0 commit comments

Comments
 (0)