Tutorial: Mesh Convergence#
This tutorial is intended to demonstrate several advanced usage features that were glossed over in previous tutorials. Specifically, this tutorial will discuss
Building a parameter study from somewhere in the middle of the SCons workflow using a common source
Utilizing
--input-fileand--output-filecommand-line argumentsUsing the
post_processing.pyscript to create multiple plots
References#
Mesh Convergence Studies [42]
SCons file objects: SCons Node Package
Environment#
SCons and WAVES can be installed in a Conda environment with the Conda package manager. See the Conda installation and Conda environment management documentation for more details about using Conda.
Note
The SALib and numpy versions may not need to be this strict for most tutorials. However, Tutorial: Sensitivity Study uncovered some undocumented SALib version sensitivity to numpy surrounding the numpy v2 rollout.
Create the tutorials environment if it doesn’t exist
$ conda create --name waves-tutorial-env --channel conda-forge waves 'scons>=4.6' matplotlib pandas pyyaml xarray seaborn 'numpy>=2' 'salib>=1.5.1' pytest
PS > conda create --name waves-tutorial-env --channel conda-forge waves scons matplotlib pandas pyyaml xarray seaborn numpy salib pytest
Activate the environment
$ conda activate waves-tutorial-env
PS > conda activate waves-tutorial-env
Some tutorials require additional third-party software that is not available for the Conda package manager. This
software must be installed separately and either made available to SConstruct by modifying your system’s PATH or by
modifying the SConstruct search paths provided to the waves.scons_extensions.add_program() method.
Directory Structure#
Create and change to a new project root directory to house the tutorial files if you have not already done so. For example
$ mkdir -p ~/waves-tutorials
$ cd ~/waves-tutorials
$ pwd
/home/roppenheimer/waves-tutorials
PS > New-Item $HOME\waves-tutorials -ItemType "Directory"
PS > Set-Location $HOME\waves-tutorials
PS > Get-Location
Path
----
C:\Users\roppenheimer\waves-tutorials
Note
If you skipped any of the previous tutorials, run the following commands to create a copy of the necessary tutorial files.
$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --overwrite --tutorial 11 && mv tutorial_11_regression_testing_SConstruct SConstruct
WAVES fetch
Destination directory: '/home/roppenheimer/waves-tutorials'
Download and copy the
tutorial_11_regression_testing.sconsfile to a new file namedtutorial_mesh_convergence.sconswith the WAVES Command-Line Utility fetch subcommand.
$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --overwrite tutorials/tutorial_11_regression_testing.scons && cp tutorial_11_regression_testing.scons tutorial_mesh_convergence.scons
WAVES fetch
Destination directory: '/home/roppenheimer/waves-tutorials'
Parameter Study File#
Create a new file
modsim_package/python/rectangle_compression_mesh_convergence.pyfrom the content below.
waves-tutorials/modsim_package/python/rectangle_compression_mesh_convergence.py
"""Parameter sets and schemas for the rectangle compression simulation."""
def parameter_schema(
global_seed: list[float] | tuple[float, ...] = (1.0, 0.5, 0.25, 0.125),
) -> dict[str, list[float]]:
"""Return mesh convergence WAVES CartesianProduct parameter schema.
:param global_seed: The global mesh seed size
:returns: WAVES CartesianProduct parameter schema
"""
schema = {
"global_seed": list(global_seed),
}
return schema
This parameter study will define a mesh convergence study where the global size of the finite elements in the model is decreased several times by a factor of two.
Mesh Convergence Selection Dictionary#
Create a new file
modsim_package/python/mesh_convergence_stress.yamlfrom the content below.
waves-tutorials/modsim_package/python/mesh_convergence_stress.yaml
'E values': 'E22'
'S values': 'S22'
'elements': 1
'step': 'Step-1'
'time': 1.0
'integration point': 0
This file defines a YAML formatted dictionary that will be used to change the default data selection of the
post_processing.py script.
SConscript#
A
diffagainst thetutorial_11_regression_testing.sconsfile from Tutorial 11: Regression Testing is included below to help identify the changes made in this tutorial. Use the diff to update yourtutorial_mesh_convergence.sconsfile, and then review the paragraphs that follow to understand the meaning of these changes.
waves-tutorials/tutorial_mesh_convergence.scons
--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_11_regression_testing.scons
+++ /home/runner/work/waves/waves/build/docs/tutorials_tutorial_mesh_convergence.scons
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-"""Rectangle compression workflow.
+"""Rectangle mesh convergence workflow.
Requires the following ``SConscript(..., exports={})``
@@ -15,7 +15,7 @@
import waves
-from modsim_package.python.rectangle_compression_cartesian_product import parameter_schema
+from modsim_package.python.rectangle_compression_mesh_convergence import parameter_schema
# Inherit the parent construction environment
Import("env")
@@ -26,6 +26,11 @@
build_directory = pathlib.Path(Dir(".").abspath)
workflow_name = build_directory.name
parameter_study_file = build_directory / "parameter_study.h5"
+simulation_constants = {
+ "width": 1.0,
+ "height": 1.0,
+ "displacement": -0.01,
+}
# Collect the target nodes to build a concise alias for all targets
workflow = []
@@ -43,34 +48,37 @@
# Comment used in tutorial code snippets: marker-3
+# Geometry
+workflow.extend(
+ env.AbaqusJournal(
+ target=["rectangle_geometry.cae", "rectangle_geometry.jnl"],
+ source=["#/modsim_package/abaqus/rectangle_geometry.py"],
+ subcommand_options="--width ${width} --height ${height}",
+ **simulation_constants,
+ )
+)
+
+# Partition
+partition_targets = env.AbaqusJournal(
+ target=["rectangle_partition.cae", "rectangle_partition.jnl"],
+ source=["#/modsim_package/abaqus/rectangle_partition.py", "rectangle_geometry.cae"],
+ subcommand_options="--width ${width} --height ${height}",
+ **simulation_constants,
+)
+workflow.extend(partition_targets)
+partition_cae_node = partition_targets[0]
+
# Parameterized targets must live inside current simulation_variables for loop
for set_name, parameters in parameter_generator.parameter_study_to_dict().items():
set_path = pathlib.Path(set_name)
- simulation_variables = parameters
+ simulation_variables = {**parameters, **simulation_constants}
# Comment used in tutorial code snippets: marker-4
- # Geometry
- workflow.extend(
- env.AbaqusJournal(
- target=[set_path / "rectangle_geometry.cae", set_path / "rectangle_geometry.jnl"],
- source=["#/modsim_package/abaqus/rectangle_geometry.py"],
- subcommand_options="--width ${width} --height ${height}",
- **simulation_variables,
- )
- )
-
- # Partition
- workflow.extend(
- env.AbaqusJournal(
- target=[set_path / "rectangle_partition.cae", set_path / "rectangle_partition.jnl"],
- source=["#/modsim_package/abaqus/rectangle_partition.py", set_path / "rectangle_geometry.cae"],
- subcommand_options="--width ${width} --height ${height}",
- **simulation_variables,
- )
- )
-
# Mesh
+ journal_options = (
+ "--global-seed ${global_seed} --input-file ${SOURCES[1].abspath} --output-file ${TARGETS[0].abspath}"
+ )
workflow.extend(
env.AbaqusJournal(
target=[
@@ -78,8 +86,11 @@
set_path / "rectangle_mesh.cae",
set_path / "rectangle_mesh.jnl",
],
- source=["#/modsim_package/abaqus/rectangle_mesh.py", set_path / "rectangle_partition.cae"],
- subcommand_options="--global-seed ${global_seed}",
+ source=[
+ "#/modsim_package/abaqus/rectangle_mesh.py",
+ partition_cae_node,
+ ],
+ subcommand_options=journal_options,
**simulation_variables,
)
)
@@ -178,16 +189,23 @@
)
)
-# Regression test
+script_options = (
+ "--input-file ${post_processing_source}"
+ " --output-file ${TARGET.file} --x-units mm --y-units MPa --x-var global_seed --y-var S"
+ " --parameter-study-file ${SOURCES[1].file}"
+ " --selection-dict ${SOURCES[2].abspath}"
+)
workflow.extend(
env.PythonScript(
- target=["regression.yaml"],
+ target=["mesh_convergence_stress.pdf", "mesh_convergence_stress.csv"],
source=[
- "#/modsim_package/python/regression.py",
- "stress_strain_comparison.csv",
- "#/modsim_package/python/rectangle_compression_cartesian_product.csv",
+ "#/modsim_package/python/post_processing.py",
+ parameter_study_file.name,
+ "#/modsim_package/python/mesh_convergence_stress.yaml",
+ *post_processing_source,
],
- subcommand_options="${SOURCES[1:].abspath} --output-file ${TARGET.abspath}",
+ subcommand_options=script_options,
+ post_processing_source=post_processing_source,
)
)
waves-tutorials/tutorial_mesh_convergence.scons
1#! /usr/bin/env python
2"""Rectangle mesh convergence workflow.
3
4Requires the following ``SConscript(..., exports={})``
5
6* ``env`` - The SCons construction environment with the following required keys
7
8 * ``datacheck_alias`` - String for the alias collecting the datacheck workflow targets
9 * ``regression_alias`` - String for the alias collecting the regression test suite targets
10 * ``unconditional_build`` - Boolean flag to force building of conditionally ignored targets
11 * ``abaqus`` - String path for the Abaqus executable
12"""
13
14import pathlib
15
16import waves
17
18from modsim_package.python.rectangle_compression_mesh_convergence import parameter_schema
19
20# Inherit the parent construction environment
21Import("env")
22
23# Comment used in tutorial code snippets: marker-1
24
25# Simulation variables
26build_directory = pathlib.Path(Dir(".").abspath)
27workflow_name = build_directory.name
28parameter_study_file = build_directory / "parameter_study.h5"
29simulation_constants = {
30 "width": 1.0,
31 "height": 1.0,
32 "displacement": -0.01,
33}
34
35# Collect the target nodes to build a concise alias for all targets
36workflow = []
37datacheck = []
The highlighted code above points out two key changes from diff at the beginning of the file. First, we import the
parameter_schema from the rectangle_compression_mesh_convergence.py file you created in the beginning of
this tutorial. The second change is the addition of a simulation_constants dictionary. This parameter study only
changes the meshing global_seed parameter, and all other model parameters stay constant. One way to achieve this
would be to set the remaining parameters as single-value parameter sets in the parameter_schema. This was done with
the global_seed and displacement parameters in Tutorial 07: Cartesian Product. Rather, we will set the
width, height, and displacement variables as constants in the SConscript file, and they will not appear
in the parameter study definition. The individual parameters from the parameter_schema and simulation_constants
dictionaries will be combined later in the SConscript file.
waves-tutorials/tutorial_mesh_convergence.scons
50# Geometry
51workflow.extend(
52 env.AbaqusJournal(
53 target=["rectangle_geometry.cae", "rectangle_geometry.jnl"],
54 source=["#/modsim_package/abaqus/rectangle_geometry.py"],
55 subcommand_options="--width ${width} --height ${height}",
56 **simulation_constants,
57 )
58)
59
60# Partition
61partition_targets = env.AbaqusJournal(
62 target=["rectangle_partition.cae", "rectangle_partition.jnl"],
63 source=["#/modsim_package/abaqus/rectangle_partition.py", "rectangle_geometry.cae"],
64 subcommand_options="--width ${width} --height ${height}",
65 **simulation_constants,
66)
67workflow.extend(partition_targets)
68partition_cae_node = partition_targets[0]
69
70# Parameterized targets must live inside current simulation_variables for loop
71for set_name, parameters in parameter_generator.parameter_study_to_dict().items():
72 set_path = pathlib.Path(set_name)
73 simulation_variables = {**parameters, **simulation_constants}
The code above is largely copy and paste from Tutorial 11: Regression Testing, with a few significant differences:
The code pertainting to
# Geometryand# Partitionhas been moved out of the parameter study’sforloop. As this parameter study only involves a meshing parameter, the Geometry and Partition workflow steps need only happen once. Then, the mesh convergence parameter study can reuserectangle_partition.caeas a common source.Note the highlighted lines for the
targetdefinitions in the# Geometryand# Partitioncode. Since this code is no longer inside of theforloop, theset_namedirectory has been dropped from thetargetdefinitions. As the first bullet alluded to, the targets for# Geometryand# Partitionwill be built in the overall build directory,build/tutorial_mesh_convergence.The following highlighted line is necessary for the parameterized
# Meshworkflow steps to reuse a common target. The SCons file object for therectangle_partition.caefile is extracted from the target list,partition_target, as a source.The final highlighted line shows how the
simulation_variablesdictionary is constructed by combining thesimulation_constantsand theglobal_seedparameters for every simulation.
waves-tutorials/tutorial_mesh_convergence.scons
77 # Mesh
78 journal_options = (
79 "--global-seed ${global_seed} --input-file ${SOURCES[1].abspath} --output-file ${TARGETS[0].abspath}"
80 )
81 workflow.extend(
82 env.AbaqusJournal(
83 target=[
84 set_path / "rectangle_mesh.inp",
85 set_path / "rectangle_mesh.cae",
86 set_path / "rectangle_mesh.jnl",
87 ],
88 source=[
89 "#/modsim_package/abaqus/rectangle_mesh.py",
90 partition_cae_node,
91 ],
92 subcommand_options=journal_options,
93 **simulation_variables,
94 )
95 )
96
97 # SolverPrep
98 copy_source_list = [
99 "#/modsim_package/abaqus/rectangle_compression.inp.in",
100 "#/modsim_package/abaqus/assembly.inp",
101 "#/modsim_package/abaqus/boundary.inp",
102 "#/modsim_package/abaqus/field_output.inp",
103 "#/modsim_package/abaqus/materials.inp",
104 "#/modsim_package/abaqus/parts.inp",
105 "#/modsim_package/abaqus/history_output.inp",
106 ]
107 workflow.extend(
108 env.CopySubstfile(
109 copy_source_list,
110 substitution_dictionary=env.SubstitutionSyntax(simulation_variables),
111 build_subdirectory=set_path,
112 )
113 )
The highlighted lines above demonstrate the usage of --input-file and --output--file command-line arguments for
the rectangle_mesh.py file. In previous tutorials, we have accepted the default values for input and output
files. In this case, however, we must specify that a common input file is used, as we want to reuse the target from the
Partition task as a source. If we would have accepted the default input file name, the rectangle_mesh.py
script would try to open a rectangle_partition.cae file in every parameter set build directory. The script
would fail to do so, because rectangle_partition.cae resides a directory above in the main build directory. We
avoid this issue by providing the absolute path to rectangle_partition.cae as the --input-file.
The --output-file command-line argument is specified in this case only for demonstration (the default value would
actually work just fine).
The highlighted line containing partition_cae_object demonstrates the usage of an SCons file object as a source.
Rather than pointing to the rectangle_partition.cae file via absolute path, we can let SCons find the file
for us in the build directory. This is achieved by simply pointing to the SCons file object that was created when we
specified rectangle_partition.cae as a target in the # Partition workflow.
waves-tutorials/tutorial_mesh_convergence.scons
175# Post-processing
176post_processing_source = [
177 pathlib.Path(set_name) / "rectangle_compression_datasets.h5"
178 for set_name in parameter_generator.parameter_study_to_dict()
179]
180script_options = "--input-file ${SOURCES[2:].abspath}"
181script_options += " --output-file ${TARGET.file} --x-units mm/mm --y-units MPa"
182script_options += " --parameter-study-file ${SOURCES[1].abspath}"
183workflow.extend(
184 env.PythonScript(
185 target=["stress_strain_comparison.pdf", "stress_strain_comparison.csv"],
186 source=["#/modsim_package/python/post_processing.py", parameter_study_file.name, *post_processing_source],
187 subcommand_options=script_options,
188 )
189)
190
191script_options = (
192 "--input-file ${post_processing_source}"
193 " --output-file ${TARGET.file} --x-units mm --y-units MPa --x-var global_seed --y-var S"
194 " --parameter-study-file ${SOURCES[1].file}"
195 " --selection-dict ${SOURCES[2].abspath}"
196)
197workflow.extend(
198 env.PythonScript(
199 target=["mesh_convergence_stress.pdf", "mesh_convergence_stress.csv"],
200 source=[
201 "#/modsim_package/python/post_processing.py",
202 parameter_study_file.name,
203 "#/modsim_package/python/mesh_convergence_stress.yaml",
204 *post_processing_source,
205 ],
206 subcommand_options=script_options,
207 post_processing_source=post_processing_source,
208 )
209)
210
211# Collector alias based on parent directory name
212env.Alias(workflow_name, workflow)
213env.Alias(f"{workflow_name}_datacheck", datacheck)
214env.Alias(env["datacheck_alias"], datacheck)
215env.Alias(env["regression_alias"], datacheck)
216
217if not env["unconditional_build"] and not env["ABAQUS_PROGRAM"]:
218 print(f"Program 'abaqus' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
219 Ignore([".", workflow_name], workflow)
220 Ignore([".", f"{workflow_name}_datacheck"], datacheck)
221 Ignore([".", env["datacheck_alias"], env["regression_alias"]], datacheck)
The highlighted code above demonstrated the usage of the post_processing.py script to generate a second plot. The first
plot, as demonstrated in Tutorial 09: Post-Processing, is a simple stress-strain comparison for each parameter
set. The highlighted code is used to generate a plot of global mesh size versus the stress in the model at the end of
the simulation. As the global mesh size decreases, the final stress should start to converge to a common value.
The specification of a selection_dict demonstrates another feature of the post_processing.py command-line
interface. In this case, the only key: value pair added to the selection_dict that does not already exist in the
post_processing.py CLI defaults is the specification of the time point 'time': 1.0. This down selects
our data to the largest compressive stress produced by the simulation, which will be our quantity of interest (QoI) for
this simulation workflow.
The remaining changes are rather simple. The --x-units and --x-var command-line arguments are updated to reflect
the usage of the global_seed parameter as the independent variable.
SConstruct#
A
diffagainst theSConstructfile from Tutorial 11: Regression Testing is included below to help identify the changes made in this tutorial. Make these changes to yourSConstructfile.
waves-tutorials/SConstruct
--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_11_regression_testing_SConstruct
+++ /home/runner/work/waves/waves/build/docs/tutorials_tutorial_mesh_convergence_SConstruct
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-"""Configure the WAVES regression testing tutorial."""
+"""Configure the WAVES mesh convergence tutorial."""
import os
import pathlib
@@ -115,6 +115,7 @@
"tutorial_08_data_extraction.scons",
"tutorial_09_post_processing.scons",
"tutorial_11_regression_testing.scons",
+ "tutorial_mesh_convergence.scons",
]
for workflow in workflow_configurations:
build_dir = env["variant_dir_base"] / pathlib.Path(workflow).stem
Build Targets#
Build the new targets
$ pwd
/home/roppenheimer/waves-tutorials
$ scons tutorial_mesh_convergence --jobs=4
The output from building the targets is not shown explicitly here, but look for one particular thing in your terminal
output. You should notice the execution of the rectangle_geometry.py and rectangle_partition.py
scripts first, and then the parameter study is kicked off with multiple executions of the rectangle_mesh.py
script.
Output Files#
Observe the catenated parameter results and parameter study dataset in the post-processing task’s STDOUT file.
$ cat build/tutorial_mesh_convergence/mesh_convergence_stress.stdout
<xarray.Dataset>
Dimensions: (E values: 4, S values: 4, elements: 64, step: 1,
time: 5, set_name: 4)
Coordinates:
* E values (E values) object 'E11' 'E22' 'E33' 'E12'
* S values (S values) object 'S11' 'S22' 'S33' 'S12'
* elements (elements) int64 1 2 3 4 5 6 7 ... 58 59 60 61 62 63 64
* step (step) object 'Step-1'
* time (time) float64 0.0175 0.07094 0.2513 0.86 1.0
integrationPoint (set_name, elements) float64 1.0 nan ... 1.0 1.0
* set_name (set_name) <U14 'parameter_set0' ... 'parameter...
set_hash (set_name) object ...
Data variables:
E (set_name, step, time, elements, E values) float32 ...
S (set_name, step, time, elements, S values) float32 ...
global_seed (set_name) float64 ...