From 9287644d6d8df227e3bf6da3af871588d56129a2 Mon Sep 17 00:00:00 2001 From: dtyagi Date: Wed, 8 Apr 2026 14:55:46 +0200 Subject: [PATCH 1/3] added the fenics examples for linear elastic plate with hole benchmark --- .github/workflows/run-benchmark-examples.yml | 54 +++ .../fenics/environment_simulation.yml | 16 + .../fenics/run_benchmark.py | 76 ++++ .../fenics/run_fenics_simulation.py | 357 ++++++++++++++++++ .../linear-elastic-plate-with-hole.zip | Bin 0 -> 9523 bytes 5 files changed, 503 insertions(+) create mode 100644 .github/workflows/run-benchmark-examples.yml create mode 100644 examples/linear-elastic-plate-with-hole/fenics/environment_simulation.yml create mode 100644 examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py create mode 100644 examples/linear-elastic-plate-with-hole/fenics/run_fenics_simulation.py create mode 100644 examples/linear-elastic-plate-with-hole/linear-elastic-plate-with-hole.zip diff --git a/.github/workflows/run-benchmark-examples.yml b/.github/workflows/run-benchmark-examples.yml new file mode 100644 index 0000000..85e71ea --- /dev/null +++ b/.github/workflows/run-benchmark-examples.yml @@ -0,0 +1,54 @@ +name: 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 + SNAKEMAKE_RESULT_FILE: metadata4ing_provenance + PROVENANACE_FILE_NAME: element_size_vs_max_mises_stress.pdf + +jobs: + run-simulation: + 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: Update environment + run: mamba env update -n model-validation -f environment_benchmarks.yml + + - name: Run Example linear-elastic-plate-with-hole_fenics using Benchmarking package + shell: bash -l {0} + run: | + cd $GITHUB_WORKSPACE/examples/linear-elastic-plate-with-hole/fenics/ + python run_benchmark.py + + - name: Archive Linear Elastic plate with a hole benchmark data for snakemake + uses: actions/upload-artifact@v4 + with: + name: snakemake_results_linear-elastic-plate-with-hole + path: | + examples/linear-elastic-plate-with-hole/fenics/results/ + + + diff --git a/examples/linear-elastic-plate-with-hole/fenics/environment_simulation.yml b/examples/linear-elastic-plate-with-hole/fenics/environment_simulation.yml new file mode 100644 index 0000000..6ed60ef --- /dev/null +++ b/examples/linear-elastic-plate-with-hole/fenics/environment_simulation.yml @@ -0,0 +1,16 @@ +name: fenics_simulation +# Environment file for fenics simulation scripts. Called by fenics tool workflow. + +channels: + - conda-forge + +channel_priority: strict + +dependencies: + - python=3.12 + - fenics-dolfinx=0.9.* + - mpich + - petsc4py + - pint + - python-gmsh + - sympy diff --git a/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py b/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py new file mode 100644 index 0000000..04d4dce --- /dev/null +++ b/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py @@ -0,0 +1,76 @@ +from pathlib import Path +import zipfile +import json +import shutil +import subprocess + +""" +The script performs the following steps: + +1. Extracts the benchmark files from a zip archive (currently assuming that it is an RO-Crate of the benchmark). +2. Iterates through the parameter configuration files, checks the "element-size" value, and if it meets the specified condition (>= 0.025) +, it executes the Snakemake workflow for that configuration. + +The results of each run (and the files used by it) are stored in the directory with the configuration name. +""" + +#################################################################################################### +#################################################################################################### +# Benchmark Extraction +#################################################################################################### +#################################################################################################### + +root_zipped_benchmark_dir = Path(__file__).resolve().parent.parent +root_unzipped_benchmark_dir = Path(__file__).resolve().parent + +with zipfile.ZipFile(root_zipped_benchmark_dir / "linear-elastic-plate-with-hole.zip", 'r') as zip_ref: + # Extract all files + zip_ref.extractall(root_unzipped_benchmark_dir) + + +#Creates a directory to store the conda environments. The environments are shared across different parameter configurations. +#To avoid redundant creation of environments, this path will be passed to all snakemake files during execution. + +shared_env_dir = root_unzipped_benchmark_dir / "conda_envs" +shared_env_dir.mkdir(parents=True, exist_ok=True) + +#################################################################################################### +#################################################################################################### +# Conditional execution of parameter configurations +#################################################################################################### +#################################################################################################### + +for file in root_unzipped_benchmark_dir.glob("parameters_*.json"): + with open(file, "r") as f: + data = json.load(f) + if data.get("element-size").get("value") >= 0.025: + #if data.get("configuration") == "1": + + # Create output directory for the configuration + output_dir = root_unzipped_benchmark_dir / "results" / data.get("configuration") + output_dir.mkdir(parents=True, exist_ok=True) + + # Copy the selected parameter file to the output directory with a standardised name + with open(output_dir / "parameters.json", "w") as outfile: + json.dump(data, outfile, indent=2) + + # Copy files from benchmark_dir to output_dir, excluding non-matching parameter files. + for item in root_unzipped_benchmark_dir.iterdir(): + if item.is_file(): + if item.name.startswith("parameters_") and item.suffix == ".json": + continue + else: + shutil.copy(item, output_dir / item.name) + + # Run the Snakemake workflow for the configuration + subprocess.run(["snakemake", "--use-conda", "--force", "--cores", "all", "--conda-prefix", str(shared_env_dir)], check=True, cwd=output_dir) + print("Workflow executed successfully.") + + # For the scenario where the snakemake workflow doesn't exist, one can directly run the simulation script using the subprocess module, e.g.: + #subprocess.run(["python", "run_fenics_simulation.py" \ + #"--input_parameter_file" "parameters.json" \ + #"--input_mesh_file" "mesh.msh" \ + #"--output_solution_file_zip" "solution_field_data.zip" \ + #"--output_metrics_file" "solution_metrics.json"], check=True, cwd=output_dir) + + #Assuming the mesh.msh and parameters.json files are present/copied to the output_dir. \ No newline at end of file diff --git a/examples/linear-elastic-plate-with-hole/fenics/run_fenics_simulation.py b/examples/linear-elastic-plate-with-hole/fenics/run_fenics_simulation.py new file mode 100644 index 0000000..6b5a114 --- /dev/null +++ b/examples/linear-elastic-plate-with-hole/fenics/run_fenics_simulation.py @@ -0,0 +1,357 @@ +import json +import sys +from argparse import ArgumentParser + +from pathlib import Path +import dolfinx as df +import basix.ufl +import numpy as np +import ufl +from dolfinx.fem.petsc import LinearProblem +from petsc4py.PETSc import ScalarType +from mpi4py import MPI +from pint import UnitRegistry + +# Add parent directory to sys.path +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from analytical_solution import AnalyticalSolution + + +def run_fenics_simulation( + parameter_file: str, mesh_file: str, solution_file_zip: str, metrics_file: str +) -> None: + ureg = UnitRegistry() + with open(parameter_file) as f: + parameters = json.load(f) + + mesh, cell_tags, facet_tags = df.io.gmshio.read_from_msh( + mesh_file, + comm=MPI.COMM_WORLD, + gdim=2, + ) + + V = df.fem.functionspace(mesh, ("CG", parameters["element-degree"], (2,))) + + tags_left = facet_tags.find(1) + tags_bottom = facet_tags.find(2) + tags_right = facet_tags.find(3) + tags_top = facet_tags.find(4) + + # Boundary conditions + dofs_left = df.fem.locate_dofs_topological(V.sub(0), 1, tags_left) + dofs_bottom = df.fem.locate_dofs_topological(V.sub(1), 1, tags_bottom) + dofs_right = df.fem.locate_dofs_topological(V, 1, tags_right) + dofs_top = df.fem.locate_dofs_topological(V, 1, tags_top) + + bc_left = df.fem.dirichletbc(0.0, dofs_left, V.sub(0)) + bc_bottom = df.fem.dirichletbc(0.0, dofs_bottom, V.sub(1)) + + E = ( + ureg.Quantity( + parameters["young-modulus"]["value"], parameters["young-modulus"]["unit"] + ) + .to_base_units() + .magnitude + ) + nu = ( + ureg.Quantity( + parameters["poisson-ratio"]["value"], parameters["poisson-ratio"]["unit"] + ) + .to_base_units() + .magnitude + ) + radius = ( + ureg.Quantity(parameters["radius"]["value"], parameters["radius"]["unit"]) + .to_base_units() + .magnitude + ) + L = ( + ureg.Quantity(parameters["length"]["value"], parameters["length"]["unit"]) + .to_base_units() + .magnitude + ) + load = ( + ureg.Quantity(parameters["load"]["value"], parameters["load"]["unit"]) + .to_base_units() + .magnitude + ) + displacement_evaluation_point = parameters["displacement-evaluation-point"] + displacement_evaluation_x = ( + ureg.Quantity( + displacement_evaluation_point["x"]["value"], + displacement_evaluation_point["x"]["unit"], + ) + .to_base_units() + .magnitude + ) + displacement_evaluation_y = ( + ureg.Quantity( + displacement_evaluation_point["y"]["value"], + displacement_evaluation_point["y"]["unit"], + ) + .to_base_units() + .magnitude + ) + + analytical_solution = AnalyticalSolution( + E=E, + nu=nu, + radius=radius, + L=L, + load=load, + ) + + def eps(v): + return ufl.sym(ufl.grad(v)) + + def sigma(v): + # plane stress + epsilon = eps(v) + return ( + E + / (1.0 - nu**2) + * ((1.0 - nu) * epsilon + nu * ufl.tr(epsilon) * ufl.Identity(2)) + ) + + def as_tensor(v): + return ufl.as_matrix([[v[0], v[2]], [v[2], v[1]]]) + + dx = ufl.Measure( + "dx", + metadata={ + "quadrature_degree": parameters["quadrature-degree"], + "quadrature_scheme": parameters["quadrature-rule"], + }, + ) + ds = ufl.Measure( + "ds", + domain=mesh, + subdomain_data=facet_tags, + ) + stress_space = df.fem.functionspace( + mesh, ("CG", parameters["element-degree"], (2, 2)) + ) + stress_function = df.fem.Function(stress_space) + + u = df.fem.Function(V, name="u") + u_prescribed = df.fem.Function(V, name="u_prescribed") + u_prescribed.interpolate(lambda x: analytical_solution.displacement(x)) + u_prescribed.x.scatter_forward() + + u_ = ufl.TestFunction(V) + v_ = ufl.TrialFunction(V) + a = df.fem.form(ufl.inner(sigma(u_), eps(v_)) * dx) + + # set rhs to zero + f = df.fem.form(ufl.inner(df.fem.Constant(mesh, np.array([0.0, 0.0])), u_) * ufl.ds) + + bc_right = df.fem.dirichletbc(u_prescribed, dofs_right) + bc_top = df.fem.dirichletbc(u_prescribed, dofs_top) + solver = LinearProblem( + a, + f, + bcs=[bc_left, bc_bottom, bc_right, bc_top], + u=u, + petsc_options={ + "ksp_type": "gmres", + "ksp_rtol": 1e-14, + "ksp_atol": 1e-14, + }, + ) + solver.solve() + + # Evaluate displacement at the specified evaluation point + displacement_eval_point = np.array( + [[displacement_evaluation_x, displacement_evaluation_y, 0.0]], + dtype=np.float64, + ) + tree = df.geometry.bb_tree(mesh, mesh.topology.dim) + cell_candidates = df.geometry.compute_collisions_points( + tree, displacement_eval_point + ) + colliding_cells = df.geometry.compute_colliding_cells( + mesh, cell_candidates, displacement_eval_point + ) + local_displacement_x = None + if len(colliding_cells.links(0)) > 0: + cell = colliding_cells.links(0)[0] + local_displacement_x = u.eval( + displacement_eval_point, np.array([cell], dtype=np.int32) + )[0] + + + def project( + v: df.fem.Function | ufl.core.expr.Expr, + V: df.fem.FunctionSpace, + dx: ufl.Measure = ufl.dx, + ) -> df.fem.Function: + """ + Calculates an approximation of `v` on the space `V` + + Args: + v: The expression that we want to evaluate. + V: The function space on which we want to evaluate. + dx: The measure that is used for the integration. This is important, if + either `V` is a quadrature space or `v` is a ufl expression containing a quadrature space. + + Returns: + A function if `u` is None, otherwise `None`. + + """ + dv = ufl.TrialFunction(V) + v_ = ufl.TestFunction(V) + a_proj = ufl.inner(dv, v_) * dx + b_proj = ufl.inner(v, v_) * dx + + solver = LinearProblem(a_proj, b_proj) + uh = solver.solve() + return uh + + plot_space_stress = df.fem.functionspace( + mesh, ("DG", parameters["element-degree"] - 1, (2, 2)) + ) + plot_space_mises = df.fem.functionspace( + mesh, ("DG", parameters["element-degree"] - 1, (1,)) + ) + stress_nodes_red = project(sigma(u), plot_space_stress, dx) + stress_nodes_red.name = "stress" + + def mises_stress(u): + stress = sigma(u) + p = ufl.tr(stress) / 3.0 + s = stress - p * ufl.Identity(2) + return ufl.as_vector([(3.0 / 2.0) ** 0.5 * (ufl.inner(s, s) + p * p) ** 0.5]) + + mises_stress_nodes = project(mises_stress(u), plot_space_mises, dx) + mises_stress_nodes.name = "von_mises_stress" + + # Write each function to its own VTK file on all ranks + output_dir = Path(solution_file_zip).parent + with df.io.VTKFile( + MPI.COMM_WORLD, + str( + output_dir + / f"solution_field_data_displacements_{parameters['configuration']}.vtk" + ), + "w", + ) as vtk: + vtk.write_function(u, 0.0) + with df.io.VTKFile( + MPI.COMM_WORLD, + str( + output_dir / f"solution_field_data_stress_{parameters['configuration']}.vtk" + ), + "w", + ) as vtk: + vtk.write_function(stress_nodes_red, 0.0) + with df.io.VTKFile( + MPI.COMM_WORLD, + str( + output_dir + / f"solution_field_data_mises_stress_{parameters['configuration']}.vtk" + ), + "w", + ) as vtk: + vtk.write_function(mises_stress_nodes, 0.0) + + # extract maximum von Mises stress + max_mises_stress_nodes = np.max(mises_stress_nodes.x.array) + + # Compute von Mises stress at quadrature (Gauss) points and extract maximum (global across MPI) + quad_element = basix.ufl.quadrature_element( + mesh.topology.cell_name(), + value_shape=(1,), + degree=parameters["quadrature-degree"], + ) + + Q_mises = df.fem.functionspace(mesh, quad_element) + mises_qp = df.fem.Function(Q_mises, name="von_mises_stress_qp") + expr_qp = df.fem.Expression(mises_stress(u), Q_mises.element.interpolation_points()) + mises_qp.interpolate(expr_qp) + max_mises_stress_gauss_points = MPI.COMM_WORLD.allreduce( + np.max(mises_qp.x.array), op=MPI.MAX + ) + + displacement_x_at_evaluation_point = None + if MPI.COMM_WORLD.rank == 0: + displacement_x_candidates = ( + MPI.COMM_WORLD.gather(local_displacement_x, root=0) or [] + ) + for value in displacement_x_candidates: + if value is not None: + displacement_x_at_evaluation_point = value + break + + if displacement_x_at_evaluation_point is None: + raise ValueError( + "Could not evaluate displacement at the configured evaluation point." + ) + else: + MPI.COMM_WORLD.gather(local_displacement_x, root=0) + + # Save metrics + metrics = { + "max_von_mises_stress_nodes": max_mises_stress_nodes, + "max_von_mises_stress_gauss_points": max_mises_stress_gauss_points, + f"displacement_x_at_evaluation_point (x={displacement_evaluation_x}, y={displacement_evaluation_y})": displacement_x_at_evaluation_point, + } + + if MPI.COMM_WORLD.rank == 0: + with open(metrics_file, "w") as f: + json.dump(metrics, f, indent=4) + # store all .vtu, .pvtu and .vtk files for this configuration in the zip file + import zipfile + + config = parameters["configuration"] + file_patterns = [ + str(output_dir / f"solution_field_data_displacements_{config}*"), + str(output_dir / f"solution_field_data_stress_{config}*"), + str(output_dir / f"solution_field_data_mises_stress_{config}*"), + ] + + files_to_store = [] + for pattern in file_patterns: + files_to_store.extend( + filter( + # filter for all file endings because this is not possible with glob + lambda path: path.suffix in [".vtk", ".vtu", ".pvtu"], + Path().glob(pattern), + ) + ) + # files_to_store.extend(Path().glob(pattern)) + with zipfile.ZipFile(solution_file_zip, "w") as zipf: + for filepath in files_to_store: + zipf.write(filepath, arcname=filepath.name) + + +if __name__ == "__main__": + parser = ArgumentParser( + description="Run FEniCS simulation for a plate with a hole.\n" + "Inputs: --input_parameter_file, --input_mesh_file\n" + "Outputs: --output_solution_file_hdf5, --output_metrics_file" + ) + parser.add_argument( + "--input_parameter_file", + required=True, + help="JSON file containing simulation parameters (input)", + ) + parser.add_argument( + "--input_mesh_file", required=True, help="Path to the mesh file (input)" + ) + parser.add_argument( + "--output_solution_file_zip", + required=True, + help="Path to the zipped solution files (output)", + ) + parser.add_argument( + "--output_metrics_file", + required=True, + help="Path to the output metrics JSON file (output)", + ) + args, _ = parser.parse_known_args() + run_fenics_simulation( + args.input_parameter_file, + args.input_mesh_file, + args.output_solution_file_zip, + args.output_metrics_file, + ) diff --git a/examples/linear-elastic-plate-with-hole/linear-elastic-plate-with-hole.zip b/examples/linear-elastic-plate-with-hole/linear-elastic-plate-with-hole.zip new file mode 100644 index 0000000000000000000000000000000000000000..9644a812e491cece1c627ccfffec5d86ecddda4f GIT binary patch literal 9523 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 literal 0 HcmV?d00001 From 5cba86dda5e8f724da057a3b1d5b8a7497e002a1 Mon Sep 17 00:00:00 2001 From: dtyagi Date: Wed, 8 Apr 2026 15:07:43 +0200 Subject: [PATCH 2/3] rewritten the CI .yml to remove ambiguity. --- .github/workflows/run-benchmark-examples.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-benchmark-examples.yml b/.github/workflows/run-benchmark-examples.yml index 85e71ea..9ec96c6 100644 --- a/.github/workflows/run-benchmark-examples.yml +++ b/.github/workflows/run-benchmark-examples.yml @@ -1,4 +1,4 @@ -name: CI +name: Examples-CI on: push: @@ -18,7 +18,7 @@ env: PROVENANACE_FILE_NAME: element_size_vs_max_mises_stress.pdf jobs: - run-simulation: + run-examples: runs-on: ubuntu-latest steps: - name: checkout repo content @@ -37,13 +37,13 @@ jobs: - name: Update environment run: mamba env update -n model-validation -f environment_benchmarks.yml - - name: Run Example linear-elastic-plate-with-hole_fenics using Benchmarking package + - name: Run linear-elastic-plate-with-hole using fenics shell: bash -l {0} run: | cd $GITHUB_WORKSPACE/examples/linear-elastic-plate-with-hole/fenics/ python run_benchmark.py - - name: Archive Linear Elastic plate with a hole benchmark data for snakemake + - name: Archive results of the fenics run of linear-elastic-plate-with-hole uses: actions/upload-artifact@v4 with: name: snakemake_results_linear-elastic-plate-with-hole From 4af3455861b61eb8ebb17a56437b958e4408e5fe Mon Sep 17 00:00:00 2001 From: dtyagi Date: Wed, 8 Apr 2026 15:18:46 +0200 Subject: [PATCH 3/3] removed comments --- examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py b/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py index 04d4dce..929fd72 100644 --- a/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py +++ b/examples/linear-elastic-plate-with-hole/fenics/run_benchmark.py @@ -44,7 +44,6 @@ with open(file, "r") as f: data = json.load(f) if data.get("element-size").get("value") >= 0.025: - #if data.get("configuration") == "1": # Create output directory for the configuration output_dir = root_unzipped_benchmark_dir / "results" / data.get("configuration")