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-file
and--output-file
command-line argumentsUsing the
post_processing.py
script to create multiple plots
References#
Mesh Convergence Studies [37]
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
Activate the environment
$ 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
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
file to a new file namedtutorial_mesh_convergence
with the WAVES Command-Line Utility fetch subcommand.
$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --overwrite tutorials/tutorial_11_regression_testing && cp tutorial_11_regression_testing tutorial_mesh_convergence
WAVES fetch
Destination directory: '/home/roppenheimer/waves-tutorials'
Parameter Study File#
Create a new file
modsim_package/python/rectangle_compression_mesh_convergence.py
from 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=[1.0, 0.5, 0.25, 0.125],
):
"""Return mesh convergence WAVES CartesianProduct parameter schema
:param float global_seed: The global mesh seed size
:returns: WAVES CartesianProduct parameter schema
:rtype: dict
"""
schema = {
"global_seed": 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.yaml
from 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
diff
against thetutorial_11_regression_testing
file 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
file, and then review the paragraphs that follow to understand the meaning of these changes.
waves-tutorials/tutorial_mesh_convergence
--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_11_regression_testing
+++ /home/runner/work/waves/waves/build/docs/tutorials_tutorial_mesh_convergence
@@ -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_name = 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_name / "rectangle_geometry.cae", set_name / "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_name / "rectangle_partition.cae", set_name / "rectangle_partition.jnl"],
- source=["#/modsim_package/abaqus/rectangle_partition.py", set_name / "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_name / "rectangle_mesh.cae",
set_name / "rectangle_mesh.jnl",
],
- source=["#/modsim_package/abaqus/rectangle_mesh.py", set_name / "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",
- ],
- subcommand_options="${SOURCES[1:].abspath} --output-file ${TARGET.abspath}",
+ "#/modsim_package/python/post_processing.py",
+ parameter_study_file.name,
+ "#/modsim_package/python/mesh_convergence_stress.yaml",
+ ]
+ + post_processing_source, # noqa: W503
+ subcommand_options=script_options,
+ post_processing_source=post_processing_source,
)
)
waves-tutorials/tutorial_mesh_convergence
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
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_name = 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
# Geometry
and# Partition
has been moved out of the parameter study’sfor
loop. 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.cae
as a common source.Note the highlighted lines for the
target
definitions in the# Geometry
and# Partition
code. Since this code is no longer inside of thefor
loop, theset_name
directory has been dropped from thetarget
definitions. As the first bullet alluded to, the targets for# Geometry
and# Partition
will be built in the overall build directory,build/tutorial_mesh_convergence
.The following highlighted line is necessary for the parameterized
# Mesh
workflow steps to reuse a common target. The SCons file object for therectangle_partition.cae
file is extracted from the target list,partition_target
, as a source.The final highlighted line shows how the
simulation_variables
dictionary is constructed by combining thesimulation_constants
and theglobal_seed
parameters for every simulation.
waves-tutorials/tutorial_mesh_convergence
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_name / "rectangle_mesh.inp",
85 set_name / "rectangle_mesh.cae",
86 set_name / "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_name,
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
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().keys()
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 ]
205 + post_processing_source, # noqa: W503
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
diff
against theSConstruct
file from Tutorial 11: Regression Testing is included below to help identify the changes made in this tutorial. Make these changes to yourSConstruct
file.
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
@@ -114,6 +114,7 @@
"tutorial_08_data_extraction",
"tutorial_09_post_processing",
"tutorial_11_regression_testing",
+ "tutorial_mesh_convergence",
]
for workflow in workflow_configurations:
build_dir = env["variant_dir_base"] / workflow
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 ...