Skip to main content
`Ctrl`+`K`

# 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

   Linux/MacOS

       $ 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

   Windows

       PS > conda create --name waves-tutorial-env --channel conda-forge waves scons matplotlib pandas pyyaml xarray seaborn numpy salib pytest

2. Activate the environment

   Linux/MacOS

       $ conda activate waves-tutorial-env

   Windows

       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

3. Create and change to a new project root directory to house the tutorial files if you have not already done so. For example

Linux/MacOS

    $ mkdir -p ~/waves-tutorials
    $ cd ~/waves-tutorials
    $ pwd
    /home/roppenheimer/waves-tutorials

Windows

    PS > New-Item $HOME\waves-tutorials -ItemType "Directory"
    PS > Set-Location $HOME\waves-tutorials
    PS > Get-Location

    Path
    ----
    C:\Users\roppenheimer\waves-tutorials

4. 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  cubit  SConstruct  sierra fierro

5. 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  cubit  SConstruct  sierra fierro

## SConscript

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

6. Review the `cubit` and `sierra` 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

      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"""
     14import pathlib
     15
     16# Inherit the parent construction environment
     17Import("env", "element_type", "solver")
     18
     19# Simulation variables
     20build_directory = pathlib.Path(Dir(".").abspath)
     21workflow_name = build_directory.name
     22
     23# Collect the target nodes to build a concise alias for all targets
     24workflow = []
     25
     26# Rectangle 2D
     27# Geometry
     28workflow.extend(
     29    env.PythonScript(
     30        target=["rectangle_geometry.cub"],
     31        source=["#/modsim_package/cubit/rectangle_geometry.py"],
     32        subcommand_options="",
     33    )
     34)
     35
     36# Partition
     37workflow.extend(
     38    env.PythonScript(
     39        target=["rectangle_partition.cub"],
     40        source=["#/modsim_package/cubit/rectangle_partition.py", "rectangle_geometry.cub"],
     41        subcommand_options="",
     42    )
     43)
     44
     45# Mesh
     46if solver.lower() == "abaqus":
     47    mesh_extension = "inp"
     48elif solver.lower() in ["sierra", "adagio"]:
     49    mesh_extension = "g"
     50else:
     51    raise RuntimeError(f"Unknown solver '{solver}'")
     52workflow.extend(
     53    env.PythonScript(
     54        target=[f"rectangle_mesh.{mesh_extension}", "rectangle_mesh.cub"],
     55        source=["#/modsim_package/cubit/rectangle_mesh.py", "rectangle_partition.cub"],
     56        subcommand_options="--element-type ${element_type} --solver ${solver}",
     57        element_type=element_type,
     58        solver=solver,
     59    )
     60)
     61
     62
     63# Cube 3D
     64# Geometry
     65workflow.extend(
     66    env.PythonScript(
     67        target=["cube_geometry.cub"],
     68        source=["#/modsim_package/cubit/cube_geometry.py"],
     69        subcommand_options="",
     70    )
     71)
     72
     73# Partition
     74workflow.extend(
     75    env.PythonScript(
     76        target=["cube_partition.cub"],
     77        source=["#/modsim_package/cubit/cube_partition.py", "cube_geometry.cub"],
     78        subcommand_options="",
     79    )
     80)
     81
     82# Mesh
     83if solver.lower() == "abaqus":
     84    mesh_extension = "inp"
     85elif solver.lower() in ["sierra", "adagio"]:
     86    mesh_extension = "g"
     87else:
     88    raise RuntimeError(f"Unknown solver '{solver}'")
     89workflow.extend(
     90    env.PythonScript(
     91        target=[f"cube_mesh.{mesh_extension}", "cube_mesh.cub"],
     92        source=["#/modsim_package/cubit/cube_mesh.py", "cube_partition.cub"],
     93        subcommand_options="--element-type ${element_type} --solver ${solver}",
     94        element_type=element_type,
     95        solver=solver,
     96    )
     97)
     98
     99# Collector alias based on parent directory name
    100env.Alias(f"{workflow_name}_cubit", workflow)
    101
    102if not env["unconditional_build"] and not env["CUBIT_PROGRAM"]:
    103    print(f"Program 'cubit' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
    104    Ignore([".", workflow_name], workflow)

waves-tutorials/tutorial_cubit/sierra

     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* ``envSierra`` - 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", "envSierra")
    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",
    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    envSierra.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 envSierra["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

7. 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

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

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

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

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

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

## Sierra Input File(s)

8. 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,7 +1,10 @@
     #! /usr/bin/env python

     import os
    +import sys
    +import typing
     import pathlib
    +import subprocess

     import waves

    @@ -48,6 +51,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

    @@ -58,6 +75,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
    @@ -69,6 +87,28 @@
     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:
    +    envSierra = 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)
    +    envSierra = Environment()
    +envSierra["sierra"] = waves.scons_extensions.add_program(envSierra, ["sierra"])

     # Comments used in tutorial code snippets: marker-4

    @@ -84,26 +124,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={})

    +envSierra.Append(
    +    BUILDERS={
    +        "Sierra": waves.scons_extensions.sierra_builder_factory(program=envSierra["sierra"]),
    +    }
    +)
    +
     # Comments used in tutorial code snippets: marker-6

     # Add simulation targets
     workflow_configurations = [
    -    "tutorial_01_geometry",
    -    "tutorial_02_partition_mesh",
    -    "tutorial_03_solverprep",
    -    "tutorial_04_simulation",
    +    "abaqus",
    +    "sierra",
    +    "fierro",
     ]
     for workflow in workflow_configurations:
         build_dir = env["variant_dir_base"] / workflow
    -    SConscript(workflow, variant_dir=build_dir, exports={"env": env}, duplicate=False)
    +    SConscript(
    +        workflow,
    +        variant_dir=build_dir,
    +        exports={"env": env, "envSierra": envSierra},
    +        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

9. 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