Tutorial: Cubit+Sierra#

Warning

Most WAVES tutorials are used as system tests in the regression test suite to ensure that the tutorial files are up-to-date and functional. The sierra local submission without sbatch is part of the regression suite, but the sbatch submission behavior is not. If you run into problems running this tutorial, please contact the WAVES development team.

Note

Depending on your Cubit installation and operating system, it may not be possible to import Cubit into arbitrary Python 3 environments. Instead, it may be necessary to execute Cubit Python scripts using the Python interpreter bundled with Cubit. An alternate Cubit tutorial using waves.scons_extensions.add_cubit_python() is provided as tutorial_cubit_alternate

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --destination tutorial_cubit_alternate tutorials/tutorial_cubit_alternate

References#

  • Cubit: Python Interface [62]

  • Cubit: Importing Cubit into Python [62]

  • Sierra: Sierra/SolidMechanics User’s Guide [64]

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.

  1. 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
    
  2. 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#

  1. 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
  1. Create a new tutorial_cubit directory with the waves fetch command below

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --destination tutorial_cubit tutorials/tutorial_cubit
$ ls tutorial_cubit
modsim_package/ abaqus.scons cubit.scons SConstruct sierra.scons fierro.scons
  1. Make the new tutorial_cubit directory the current working directory

$ pwd
/home/roppenheimer/waves-tutorials
$ cd tutorial_cubit
$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ ls
modsim_package/ abaqus.scons cubit.scons SConstruct sierra.scons fierro.scons

SConscript#

Note that the tutorial_cubit directory has four SConscript files: cubit.scons, abaqus.scons, sierra.scons, and fierro.scons. The cubit.scons and sierra.scons files are relevant to the current tutorial. The abaqus.scons and fierro.scons workflows are described in the complementary Tutorial: Cubit+Abaqus and Tutorial: Cubit+Fierro.

  1. Review the cubit.scons and sierra.scons tutorials and compare them against the Tutorial 04: Simulation files.

The structure has changed enough that a diff view is not as useful. Instead the contents of the new SConscript files are duplicated below.

waves-tutorials/tutorial_cubit/cubit.scons

  1#! /usr/bin/env python
  2"""Rectangle compression workflow: geometry, partition, mesh.
  3
  4Requires the following ``SConscript(..., exports={})``
  5
  6* ``env`` - The SCons construction environment with the following required keys
  7
  8  * ``unconditional_build`` - Boolean flag to force building of conditionally ignored targets
  9  * ``cubit`` - String path for the Cubit executable
 10
 11* ``element_type`` - The Cubit 4 node quadrilateral element type
 12* ``solver`` - The target solver to use when writing a mesh file
 13"""
 14
 15import pathlib
 16
 17# Inherit the parent construction environment
 18Import("env", "element_type", "solver")
 19
 20# Simulation variables
 21build_directory = pathlib.Path(Dir(".").abspath)
 22workflow_name = build_directory.name
 23
 24# Collect the target nodes to build a concise alias for all targets
 25workflow = []
 26
 27# Rectangle 2D
 28# Geometry
 29workflow.extend(
 30    env.PythonScript(
 31        target=["rectangle_geometry.cub"],
 32        source=["#/modsim_package/cubit/rectangle_geometry.py"],
 33        subcommand_options="",
 34    )
 35)
 36
 37# Partition
 38workflow.extend(
 39    env.PythonScript(
 40        target=["rectangle_partition.cub"],
 41        source=["#/modsim_package/cubit/rectangle_partition.py", "rectangle_geometry.cub"],
 42        subcommand_options="",
 43    )
 44)
 45
 46# Mesh
 47if solver.lower() == "abaqus":
 48    mesh_extension = "inp"
 49elif solver.lower() in ["sierra", "adagio"]:
 50    mesh_extension = "g"
 51else:
 52    raise RuntimeError(f"Unknown solver '{solver}'")
 53workflow.extend(
 54    env.PythonScript(
 55        target=[f"rectangle_mesh.{mesh_extension}", "rectangle_mesh.cub"],
 56        source=["#/modsim_package/cubit/rectangle_mesh.py", "rectangle_partition.cub"],
 57        subcommand_options="--element-type ${element_type} --solver ${solver}",
 58        element_type=element_type,
 59        solver=solver,
 60    )
 61)
 62
 63
 64# Cube 3D
 65# Geometry
 66workflow.extend(
 67    env.PythonScript(
 68        target=["cube_geometry.cub"],
 69        source=["#/modsim_package/cubit/cube_geometry.py"],
 70        subcommand_options="",
 71    )
 72)
 73
 74# Partition
 75workflow.extend(
 76    env.PythonScript(
 77        target=["cube_partition.cub"],
 78        source=["#/modsim_package/cubit/cube_partition.py", "cube_geometry.cub"],
 79        subcommand_options="",
 80    )
 81)
 82
 83# Mesh
 84if solver.lower() == "abaqus":
 85    mesh_extension = "inp"
 86elif solver.lower() in ["sierra", "adagio"]:
 87    mesh_extension = "g"
 88else:
 89    raise RuntimeError(f"Unknown solver '{solver}'")
 90workflow.extend(
 91    env.PythonScript(
 92        target=[f"cube_mesh.{mesh_extension}", "cube_mesh.cub"],
 93        source=["#/modsim_package/cubit/cube_mesh.py", "cube_partition.cub"],
 94        subcommand_options="--element-type ${element_type} --solver ${solver}",
 95        element_type=element_type,
 96        solver=solver,
 97    )
 98)
 99
100# Collector alias based on parent directory name
101env.Alias(f"{workflow_name}_cubit", workflow)
102
103if not env["unconditional_build"] and not env["CUBIT_PROGRAM"]:
104    print(f"Program 'cubit' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
105    Ignore([".", workflow_name], workflow)

waves-tutorials/tutorial_cubit/sierra.scons

 1#! /usr/bin/env python
 2"""Rectangle compression workflow: Sierra solve.
 3
 4Requires the following ``SConscript(..., exports={})``
 5
 6* ``env`` - The SCons construction environment with the following required keys
 7
 8  * ``unconditional_build`` - Boolean flag to force building of conditionally ignored targets
 9  * ``cubit`` - String path for the Cubit executable
10
11* ``env_sierra`` - The Sierra SCons construction environment with the following required keys
12
13  * ``sierra`` - String path for the Sierra executable
14"""
15
16import pathlib
17
18# Inherit the parent construction environment
19Import("env", "env_sierra")
20
21# Simulation variables
22build_directory = pathlib.Path(Dir(".").abspath)
23workflow_name = build_directory.name
24
25# Collect the target nodes to build a concise alias for all targets
26workflow = []
27
28element_type = "SHELL"
29solver = "sierra"
30SConscript(
31    "cubit.scons",
32    exports={"env": env, "element_type": element_type, "solver": solver},
33    duplicate=False,
34)
35
36# SolverPrep
37sierra_source_list = ["#/modsim_package/sierra/rectangle_compression.i"]
38sierra_source_list = [pathlib.Path(source_file) for source_file in sierra_source_list]
39workflow.extend(env.CopySubstfile(sierra_source_list))
40
41# Sierra Solve
42solve_source_list = [source_file.name for source_file in sierra_source_list]
43solve_source_list.append("rectangle_mesh.g")
44workflow.extend(
45    env_sierra.Sierra(
46        target=["rectangle_compression.e"],
47        source=solve_source_list,
48    )
49)
50
51# Collector alias based on parent directory name
52env.Alias(workflow_name, workflow)
53
54# Developer note: CI system configuration for Sierra is inconsistent. Do not force with ``unconditional build`` option.
55if not env_sierra["sierra"]:
56    print(f"Program 'sierra' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
57    Ignore([".", workflow_name], workflow)

Cubit Journal Files#

  1. Review the following journal files in the waves-tutorials/modsim_package/cubit directory.

The Cubit journal files include the same CLI introduced in Tutorial 02: Partition and Mesh for the Abaqus journal files. Besides the differences in Abaqus and Cubit commands, the major difference between the Abaqus and Cubit journal files is the opportunity to use Python 3 with Cubit, where Abaqus journal files must use the Abaqus controlled installation of Python 2. The API and CLI built from the Cubit journal files’ docstrings may be found in the WAVES-TUTORIAL API for cubit and the WAVES-TUTORIAL CLI for cubit, respectively.

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_geometry.py

 1"""Create a simple rectangle geometry."""
 2
 3import argparse
 4import pathlib
 5import sys
 6
 7import cubit
 8
 9
10def main(output_file: pathlib.Path, width: float, height: float) -> None:
11    """Create a simple rectangle geometry.
12
13    This script creates a simple Cubit model with a single rectangle part.
14
15    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` will be
16        used.
17    :param float width: The rectangle width
18    :param float height: The rectangle height
19
20    :returns: writes ``output_file``.cub
21    """
22    output_file = output_file.with_suffix(".cub")
23
24    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
25    cubit.cmd("new")
26    cubit.cmd("reset")
27
28    cubit.cmd("create vertex 0 0 0")
29    cubit.cmd(f"create vertex {width} 0 0")
30    cubit.cmd(f"create vertex {width} {height} 0")
31    cubit.cmd(f"create vertex 0 {height} 0")
32    cubit.cmd("create surface vertex 1,2,3,4")
33
34    cubit.cmd(f"save as '{output_file}' overwrite")
35
36
37def get_parser() -> argparse.ArgumentParser:
38    """Return the command-line interface parser."""
39    script_name = pathlib.Path(__file__)
40    # Set default parameter values
41    default_output_file = script_name.with_suffix(".cub").name
42    default_width = 1.0
43    default_height = 1.0
44
45    prog = f"python {script_name.name} "
46    cli_description = "Create a simple rectangle geometry and write an ``output_file``.cub Cubit model file."
47    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
48    parser.add_argument(
49        "--output-file",
50        type=pathlib.Path,
51        default=default_output_file,
52        help=(
53            "The output file for the Cubit model. "
54            "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub "
55            "(default: %(default)s"
56        ),
57    )
58    parser.add_argument(
59        "--width",
60        type=float,
61        default=default_width,
62        help="The rectangle width",
63    )
64    parser.add_argument(
65        "--height",
66        type=float,
67        default=default_height,
68        help="The rectangle height",
69    )
70    return parser
71
72
73if __name__ == "__main__":
74    parser = get_parser()
75    args = parser.parse_args()
76    sys.exit(
77        main(
78            output_file=args.output_file,
79            width=args.width,
80            height=args.height,
81        )
82    )

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_partition.py

  1"""Partition the simple rectangle geometry created by ``rectangle_geometry.py``."""
  2
  3import argparse
  4import pathlib
  5import shutil
  6import sys
  7
  8import cubit
  9
 10
 11def main(input_file: pathlib.Path, output_file: pathlib.Path, width: float, height: float) -> None:
 12    """Partition the simple rectangle geometry created by ``rectangle_geometry.py``.
 13
 14    This script partitions a simple Cubit model with a single rectangle part.
 15
 16    **Feature labels:**
 17
 18    * ``bottom_left`` - bottom left vertex
 19    * ``bottom_right`` - bottom right vertex
 20    * ``top_right`` - top right vertex
 21    * ``top_left`` - top left vertex
 22    * ``left`` - left edge nodes
 23    * ``top`` - top edge nodes
 24    * ``right`` - right edge nodes
 25    * ``bottom`` - bottom edge nodes
 26    * ``elset_left`` - left edge elements
 27    * ``elset_top`` - top edge elements
 28    * ``elset_right`` - right edge elements
 29    * ``elset_bottom`` - bottom edge elements
 30
 31    :param str input_file: The Cubit model file created by ``rectangle_geometry.py``. Will be stripped of the extension
 32        and ``.cub`` will be used.
 33    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` will be
 34        used.
 35    :param float width: The rectangle width
 36    :param float height: The rectangle height
 37
 38    :returns: writes ``output_file``.cub
 39    """
 40    input_file = input_file.with_suffix(".cub")
 41    output_file = output_file.with_suffix(".cub")
 42
 43    # Avoid modifying the contents or timestamp on the input file.
 44    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 45    if input_file != output_file:
 46        shutil.copyfile(input_file, output_file)
 47
 48    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
 49    cubit.cmd("new")
 50    cubit.cmd("reset")
 51
 52    cubit.cmd(f"open '{output_file}'")
 53
 54    cubit.cmd("nodeset 1 add vertex 1")
 55    cubit.cmd("nodeset 1 name 'bottom_left'")
 56    cubit.cmd("nodeset 2 add vertex 2")
 57    cubit.cmd("nodeset 2 name 'bottom_right'")
 58    cubit.cmd("nodeset 3 add vertex 3")
 59    cubit.cmd("nodeset 3 name 'top_right'")
 60    cubit.cmd("nodeset 4 add vertex 4")
 61    cubit.cmd("nodeset 4 name 'top_left'")
 62
 63    cubit.cmd("nodeset 5 add curve 4")
 64    cubit.cmd("nodeset 5 name 'left'")
 65    cubit.cmd("nodeset 6 add curve 3")
 66    cubit.cmd("nodeset 6 name 'top'")
 67    cubit.cmd("nodeset 7 add curve 2")
 68    cubit.cmd("nodeset 7 name 'right'")
 69    cubit.cmd("nodeset 8 add curve 1")
 70    cubit.cmd("nodeset 8 name 'bottom'")
 71
 72    cubit.cmd("sideset 1 add curve 4")
 73    cubit.cmd("sideset 1 name 'elset_left'")
 74    cubit.cmd("sideset 2 add curve 3")
 75    cubit.cmd("sideset 2 name 'elset_top'")
 76    cubit.cmd("sideset 3 add curve 2")
 77    cubit.cmd("sideset 3 name 'elset_right'")
 78    cubit.cmd("sideset 4 add curve 1")
 79    cubit.cmd("sideset 4 name 'elset_bottom'")
 80
 81    cubit.cmd(f"save as '{output_file}' overwrite")
 82
 83
 84def get_parser() -> argparse.ArgumentParser:
 85    """Return the command-line interface parser."""
 86    script_name = pathlib.Path(__file__)
 87    # Set default parameter values
 88    default_input_file = script_name.with_suffix(".cub").name.replace("_partition", "_geometry")
 89    default_output_file = script_name.with_suffix(".cub").name
 90    default_width = 1.0
 91    default_height = 1.0
 92
 93    prog = f"python {script_name.name} "
 94    cli_description = (
 95        "Partition the simple rectangle geometry created by ``rectangle_geometry.py`` "
 96        "and write an ``output_file``.cub Cubit model file."
 97    )
 98    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
 99    parser.add_argument(
100        "--input-file",
101        type=pathlib.Path,
102        default=default_input_file,
103        help=(
104            "The Cubit model file created by ``rectangle_geometry.py``. "
105            "Will be stripped of the extension and ``.cub`` will be used, e.g. ``input_file``.cub "
106            "(default: %(default)s"
107        ),
108    )
109    parser.add_argument(
110        "--output-file",
111        type=pathlib.Path,
112        default=default_output_file,
113        help=(
114            "The output file for the Cubit model. "
115            "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub "
116            "(default: %(default)s"
117        ),
118    )
119    parser.add_argument(
120        "--width",
121        type=float,
122        default=default_width,
123        help="The rectangle width",
124    )
125    parser.add_argument(
126        "--height",
127        type=float,
128        default=default_height,
129        help="The rectangle height",
130    )
131    return parser
132
133
134if __name__ == "__main__":
135    parser = get_parser()
136    args = parser.parse_args()
137    sys.exit(
138        main(
139            input_file=args.input_file,
140            output_file=args.output_file,
141            width=args.width,
142            height=args.height,
143        )
144    )

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_mesh.py

  1"""Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py``."""
  2
  3import argparse
  4import pathlib
  5import shutil
  6import sys
  7
  8import cubit
  9
 10
 11def main(
 12    input_file: pathlib.Path,
 13    output_file: pathlib.Path,
 14    global_seed: float,
 15    element_type: str = "QUAD",
 16    solver: str = "abaqus",
 17) -> None:
 18    """Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py``.
 19
 20    This script meshes a simple Cubit model with a single rectangle part.
 21
 22    **Feature labels:**
 23
 24    * ``NODES`` - all part nodes
 25    * ``ELEMENTS`` - all part elements
 26
 27    :param str input_file: The Cubit model file created by ``rectangle_partition.py``. Will be stripped of the extension
 28        and ``.cub`` will be used.
 29    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` and
 30        ``.inp`` will be used for the model and orphan mesh output files, respectively.
 31    :param float global_seed: The global mesh seed size
 32    :param str element_type: The model element type. Must be a supported Cubit 4 node element type.
 33    :param str solver: The solver type to use when exporting the mesh
 34
 35    :returns: writes ``output_file``.cub and ``output_file``.inp
 36
 37    :raises RuntimeError: If the solver is not supported
 38    """
 39    input_file = pathlib.Path(input_file).with_suffix(".cub")
 40    output_file = pathlib.Path(output_file).with_suffix(".cub")
 41    abaqus_mesh_file = output_file.with_suffix(".inp")
 42    sierra_mesh_file = output_file.with_suffix(".g")
 43
 44    # Avoid modifying the contents or timestamp on the input file.
 45    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 46    if input_file != output_file:
 47        shutil.copyfile(input_file, output_file)
 48
 49    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
 50    cubit.cmd("new")
 51    cubit.cmd("reset")
 52
 53    cubit.cmd(f"open '{output_file}'")
 54
 55    cubit.cmd(f"surface 1 size {global_seed}")
 56    cubit.cmd("mesh surface 1")
 57    cubit.cmd("set duplicate block elements off")
 58
 59    cubit.cmd("nodeset 9 add surface 1")
 60    cubit.cmd("nodeset 9 name 'NODES'")
 61
 62    cubit.cmd("block 1 add surface 1")
 63    cubit.cmd(f"block 1 name 'ELEMENTS' Element type {element_type}")
 64
 65    cubit.cmd(f"save as '{output_file}' overwrite")
 66
 67    if solver.lower() == "abaqus":
 68        # Export Abaqus orphan mesh for Abaqus workflow
 69        cubit.cmd(f"export abaqus '{abaqus_mesh_file}' partial dimension 2 block 1 overwrite everything")
 70    elif solver.lower() in ["sierra", "adagio"]:
 71        # Export Genesis file for Sierra workflow
 72        cubit.cmd(f"export mesh '{sierra_mesh_file}' overwrite")
 73    else:
 74        raise RuntimeError(f"Uknown solver '{solver}'")
 75
 76
 77def get_parser() -> argparse.ArgumentParser:
 78    """Return the command-line interface parser."""
 79    script_name = pathlib.Path(__file__)
 80    # Set default parameter values
 81    default_input_file = script_name.with_suffix(".cub").name.replace("_mesh", "_partition")
 82    default_output_file = script_name.with_suffix(".cub").name
 83    default_global_seed = 1.0
 84    default_element_type = "QUAD"
 85    default_solver = "abaqus"
 86
 87    prog = f"python {script_name.name} "
 88    cli_description = (
 89        "Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py`` "
 90        "and write an ``output_file``.cub Cubit model file and ``output_file``.inp orphan mesh file."
 91    )
 92    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
 93    parser.add_argument(
 94        "--input-file",
 95        type=pathlib.Path,
 96        default=default_input_file,
 97        help=(
 98            "The Cubit model file created by ``rectangle_partition.py``. "
 99            "Will be stripped of the extension and ``.cub`` will be used, e.g. ``input_file``.cub "
100            "(default: %(default)s"
101        ),
102    )
103    parser.add_argument(
104        "--output-file",
105        type=pathlib.Path,
106        default=default_output_file,
107        help=(
108            "The output file for the Cubit model. "
109            "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub"
110        ),
111    )
112    parser.add_argument(
113        "--global-seed",
114        type=float,
115        default=default_global_seed,
116        help="The global mesh seed size (default: %(default)s)",
117    )
118    parser.add_argument(
119        "--element-type",
120        type=str,
121        default=default_element_type,
122        help="The model element type. Must be a supported Cubit 4 node element type. (default: %(default)s)",
123    )
124    parser.add_argument(
125        "--solver",
126        type=str,
127        default=default_solver,
128        choices=["abaqus", "sierra", "adagio"],
129        help="The target solver for the mesh file. (default: %(default)s)",
130    )
131
132    return parser
133
134
135if __name__ == "__main__":
136    parser = get_parser()
137    args = parser.parse_args()
138    sys.exit(
139        main(
140            input_file=args.input_file,
141            output_file=args.output_file,
142            global_seed=args.global_seed,
143            element_type=args.element_type,
144            solver=args.solver,
145        )
146    )

Sierra Input File(s)#

  1. Create or review the Sierra input file from the contents below

waves-tutorials/tutorial_cubit/modsim_package/sierra/rectangle_compression.i

  1begin sierra
  2
  3  begin function line
  4    type is analytic
  5    evaluate expression = "x"
  6  end function line
  7
  8  begin property specification for material mock
  9    density = 0.27000e-08
 10    begin parameters for model elastic
 11      youngs modulus = 100
 12      poissons ratio = 0.3
 13    end
 14  end
 15
 16  begin shell section planestress
 17    thickness = 0.1
 18  end shell section planestress
 19
 20  begin finite element model rectangle_compression
 21    database name = rectangle_mesh.g
 22    database type = exodusII
 23
 24    begin parameters for block ELEMENTS
 25      material mock
 26      solid mechanics use model elastic
 27      section = planestress
 28    end
 29
 30  end finite element model rectangle_compression
 31
 32  begin adagio procedure adagio
 33
 34    begin time control
 35      begin time stepping block p0
 36        start time = 0
 37        begin parameters for adagio region adagio
 38            time increment = 0.5
 39        end
 40      end
 41      termination time = 1
 42    end
 43
 44    begin adagio region adagio
 45      use finite element model rectangle_compression
 46
 47      begin results output output_rectangle_compression
 48        database name = rectangle_compression.e
 49        database type = exodusII
 50        at time 0 interval = 0.1
 51        global variables = kinetic_energy
 52        global variables = internal_energy
 53        global variables = external_energy
 54        global variables = von_mises_max
 55        global variables = effective_strain_max
 56        nodal variables = displacement
 57        nodal variables = force_external
 58        nodal variables = force_internal
 59        element variables = strain
 60        element variables = unrotated_stress
 61        element variables = effective_strain
 62        element variables = von_mises
 63      end
 64
 65      begin user output
 66        include all blocks
 67        compute global von_mises_max as max absolute value of element von_mises
 68        compute global effective_strain_max as max absolute value of element effective_strain
 69      end user output
 70
 71      begin fixed displacement
 72        node set = bottom_left
 73        component = x
 74      end
 75
 76      begin fixed displacement
 77        node set = bottom_left
 78        component = y
 79      end
 80
 81      begin fixed rotation
 82        node set = bottom_left
 83        component = x
 84      end
 85
 86      begin fixed rotation
 87        node set = bottom_left
 88        component = y
 89      end
 90
 91      begin fixed rotation
 92        node set = bottom_left
 93        component = z
 94      end
 95
 96      begin fixed displacement
 97        node set = bottom
 98        component = y
 99      end
100
101      begin prescribed displacement
102        node set = top
103        component = y
104        function = line
105        scale factor = -0.01
106      end
107
108      ## TODO: Do we need to add traction BC for plane stress? Is there a plane stress element?
109
110      begin solver
111        begin cg
112          reference = external
113          target relative residual = 1e-10
114          begin full tangent preconditioner
115            iteration update = 10
116          end full tangent preconditioner
117        end cg
118      end solver
119
120    end adagio region adagio
121
122  end adagio procedure adagio
123
124end sierra

SConstruct#

Note that Sierra requires a separate construction environment from the launching Conda environment. This is because Sierra ships with a version of Python that conflicts with the launching Conda environment. You may need to update the Sierra activation shell command according to the instructions on your local system.

A diff against the SConstruct file from Tutorial 04: Simulation is included below to help identify the changes made in this tutorial.

waves-tutorials/tutorial_cubit/SConstruct

--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_04_simulation_SConstruct
+++ /home/runner/work/waves/waves/build/docs/tutorial_cubit_SConstruct
@@ -1,8 +1,11 @@
 #! /usr/bin/env python
-"""Configure the WAVES simulation tutorial."""
+"""Configure the WAVES Cubit+Abaqus/Fierro/Sierra tutorial."""
 
 import os
 import pathlib
+import subprocess
+import sys
+import typing
 
 import waves
 
@@ -49,6 +52,20 @@
     metavar="COMMAND",
     help=f"Override for the Abaqus command. Repeat to specify more than one (default: {default_abaqus_commands})",
 )
+default_cubit_commands = [
+    "/apps/Cubit-16.12/cubit",
+    "/usr/projects/ea/Cubit/Cubit-16.12/cubit",
+    "cubit",
+]
+AddOption(
+    "--cubit-command",
+    dest="cubit_command",
+    nargs=1,
+    type="string",
+    action="append",
+    metavar="COMMAND",
+    help=f"Override for the Cubit command. Repeat to specify more than one (default: {default_cubit_commands})",
+)
 
 # Comments used in tutorial code snippets: marker-2
 
@@ -59,6 +76,7 @@
     unconditional_build=GetOption("unconditional_build"),
     print_build_failures=GetOption("print_build_failures"),
     abaqus_commands=GetOption("abaqus_command"),
+    cubit_commands=GetOption("cubit_command"),
 )
 
 # Conditionally print failed task *.stdout files
@@ -70,6 +88,30 @@
 env["ABAQUS_PROGRAM"] = env.AddProgram(
     env["abaqus_commands"] if env["abaqus_commands"] is not None else default_abaqus_commands
 )
+env["CUBIT_PROGRAM"] = env.AddCubit(
+    env["cubit_commands"] if env["cubit_commands"] is not None else default_cubit_commands
+)
+env["FIERRO_IMPLICIT_PROGRAM"] = env.AddProgram(["fierro-parallel-implicit"])
+
+
+# Sierra requires a separate construction environment
+def return_modulepath(modules: typing.Iterable[str]) -> pathlib.Path:
+    """Return parent path of first found module or the current working directory."""
+    return next((pathlib.Path(path).parent for path in modules if pathlib.Path(path).exists()), pathlib.Path())
+
+
+sierra_modules = [
+    "/projects/aea_compute/modulefiles/sierra/5.21.6",
+]
+sierra_modulefile_parent = return_modulepath(sierra_modules)
+try:
+    env_sierra = waves.scons_extensions.shell_environment(
+        f"module use {sierra_modulefile_parent} && module load sierra"
+    )
+except subprocess.CalledProcessError:
+    print("Failed to initialize the Sierra environment", file=sys.stderr)
+    env_sierra = Environment()
+env_sierra["sierra"] = waves.scons_extensions.add_program(env_sierra, ["sierra"])
 
 # Comments used in tutorial code snippets: marker-4
 
@@ -85,26 +127,33 @@
 for key, value in project_variables.items():
     env[key] = value
 
-# Make the project package importable for Python and Abaqus Python environments
-env.PrependENVPath("PYTHONPATH", project_dir)
-
 # Comments used in tutorial code snippets: marker-5
 
 # Add builders and pseudo-builders
 env.Append(BUILDERS={})
 
+env_sierra.Append(
+    BUILDERS={
+        "Sierra": waves.scons_extensions.sierra_builder_factory(program=env_sierra["sierra"]),
+    }
+)
+
 # Comments used in tutorial code snippets: marker-6
 
 # Add simulation targets
 workflow_configurations = [
-    "tutorial_01_geometry.scons",
-    "tutorial_02_partition_mesh.scons",
-    "tutorial_03_solverprep.scons",
-    "tutorial_04_simulation.scons",
+    "abaqus.scons",
+    "sierra.scons",
+    "fierro.scons",
 ]
 for workflow in workflow_configurations:
     build_dir = env["variant_dir_base"] / pathlib.Path(workflow).stem
-    SConscript(workflow, variant_dir=build_dir, exports={"env": env}, duplicate=False)
+    SConscript(
+        workflow,
+        variant_dir=build_dir,
+        exports={"env": env, "env_sierra": env_sierra},
+        duplicate=False,
+    )
 
 # Comments used in tutorial code snippets: marker-7
 

Note that the Cubit Python files don’t perform any imports from the current modsim project package, so the PYTHONPATH modification is no longer required. This tutorial is created in a new, stand-alone subdirectory, so the previous tutorial workflow configurations are no longer available. Only the sierra and abaqus workflow configurations will be found by SCons at execution time. Finally, note that the cubit SConscript file is not called by the SConstruct file. Instead, the cubit configuration is reused by the sierra and abaqus workflows, so the Cubit tasks only need to be defined once. To handle this task reuse some additional variable import and export statements are required by the cubit configuration file.

Build Targets#

  1. Build the new targets

$ pwd
/path/to/waves-tutorials/tutorial_cubit
$ scons sierra
scons: Reading SConscript files ...
Checking whether /apps/abaqus/Commands/abq2024 program exists.../apps/abaqus/Commands/abq2024
Checking whether abq2024 program exists...no
Checking whether /apps/Cubit-16.12/cubit program exists.../apps/Cubit-16.12/cubit
Checking whether cubit program exists...no
Checking whether sierra program exists.../projects/sierra/sierra5121/install/tools/sntools/engine/sierra
scons: done reading SConscript files.
scons: Building targets ...
Copy("build/sierra/rectangle_compression.i", "modsim_package/sierra/rectangle_compression.i")
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/sierra && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_geometry.py > rectangle_geometry.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/sierra && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_partition.py > rectangle_partition.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/sierra && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_mesh.py --element-type SHELL --solver sierra > rectangle_mesh.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/sierra && /projects/sierra/sierra5121/install/tools/sntools/engine/sierra adagio -i rectangle_compression.i > rectangle_compression.stdout 2>&1
scons: done building targets.

Output Files#

Explore the contents of the build directory using the tree command against the build/abaqus directory, as shown below.

$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ tree build/sierra/
build/sierra/
|-- rectangle_compression.e
|-- rectangle_compression.i
|-- rectangle_compression.log
|-- rectangle_compression.stdout
|-- rectangle_geometry.cub
|-- rectangle_geometry.stdout
|-- rectangle_mesh.cub
|-- rectangle_mesh.g
|-- rectangle_mesh.stdout
|-- rectangle_partition.cub
`-- rectangle_partition.stdout

0 directories, 11 files