Tutorial: Cubit+Fierro#

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#

Environment#

Warning

The Fierro tutorial requires a different compute environment than the other tutorials. The following commands create a dedicated environment for the use of this tutorial. You can also use your existing tutorial environment environment if you add the FierroMechanics channel and install the fierro-fe-cpu and meshio packages.

SCons, WAVES, and Fierro 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.

  1. Create the Fierro tutorials environment if it doesn’t exist

    $ conda create --name waves-fierro-env --channel fierromechanics --channel conda-forge waves 'scons>=4.6' fierro-fe-cpu meshio
    
  2. Activate the environment

    $ conda activate waves-fierro-env
    

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 fierro.scons files are relevant to the current tutorial. The abaqus.scons and sierra.scons workflows are described in the complementary Tutorial: Cubit+Abaqus and Tutorial: Cubit+Sierra.

  1. Review the cubit.scons and fierro.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/fierro.scons

 1#! /usr/bin/env python
 2"""Rectangle compression workflow: Fierro 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
12import pathlib
13
14# Inherit the parent construction environment
15Import("env")
16
17# Simulation variables
18build_directory = pathlib.Path(Dir(".").abspath)
19workflow_name = build_directory.name
20
21# Collect the target nodes to build a concise alias for all targets
22workflow = []
23
24element_type = "HEX"
25solver = "sierra"
26SConscript("cubit.scons", exports={"env": env, "element_type": element_type, "solver": solver}, duplicate=False)
27
28# Convert mesh file type for Fierro
29env.PythonScript(
30    target=["cube_mesh.vtk"],
31    source=["#/modsim_package/fierro/convert_to_vtk2ascii.py", "cube_mesh.g"],
32    subcommand_options="--input-format=exodus ${SOURCES[1].abspath} ${TARGET.abspath}",
33)
34
35# SolverPrep
36fierro_source_list = ["#/modsim_package/fierro/cube_compression.yaml"]
37fierro_source_list = [pathlib.Path(source_file) for source_file in fierro_source_list]
38workflow.extend(env.CopySubstfile(fierro_source_list))
39
40# Fierro Solve
41solve_source_list = [source_file.name for source_file in fierro_source_list]
42solve_source_list.append("cube_mesh.vtk")
43workflow.extend(
44    env.FierroImplicit(
45        target=["cube_compression.stdout", "TecplotTO0.dat", "TecplotTO_undeformed0.dat"],
46        source=solve_source_list,
47    )
48)
49
50# Collector alias based on parent directory name
51env.Alias(workflow_name, workflow)
52
53if not env["unconditional_build"] and not env["FIERRO_IMPLICIT_PROGRAM"]:
54    print(f"Program 'fierro' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
55    Ignore([".", workflow_name], workflow)

Cubit Journal Files#

Unlike Tutorial: Cubit+Abaqus and Tutorial: Cubit+Sierra, this tutorial creates a 3D cube geometry. The Fierro implicit solver works best with 3D geometries and meshes. The 3D geometry, mesh, and simulation match the uniaxial compression boundary conditions of the 2D simulation as closely as possible with the Fierro boundary conditions.

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

The Cubit journal files include a similar CLI to the Abaqus journal files introduced in Tutorial 02: Partition and Mesh 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/cube_geometry.py

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

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

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

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

  1"""Mesh the simple cube geometry partitioned by ``cube_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 cube geometry partitioned by ``cube_partition.py``.
 19
 20    This script meshes a simple Cubit model with a single cube 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 ``cube_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"volume 1 size {global_seed}")
 56    cubit.cmd("mesh volume 1")
 57    cubit.cmd("set duplicate block elements off")
 58
 59    cubit.cmd("nodeset 9 add volume 1")
 60    cubit.cmd("nodeset 9 name 'NODES'")
 61
 62    cubit.cmd("block 1 add volume 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 = "HEX"
 85    default_solver = "abaqus"
 86
 87    prog = f"python {script_name.name} "
 88    cli_description = (
 89        "Mesh the simple cube geometry partitioned by ``cube_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 ``cube_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    )

Python Script#

Fierro doesn’t read Sierra formatted (Genesis/Exodus) meshes natively. We need to convert from the Cubit Genesis mesh to an older vtk ASCII text mesh file with meshio. The Fierro development team provided a draft conversion script that has been updated here to use a similar CLI to meshio.

waves-tutorials/tutorial_cubit/modsim_package/fierro/convert_to_vtk2ascii.py

 1"""Convert VTK mesh files to the older VTK ASCII version 2 files required by Fierro."""
 2
 3import argparse
 4import pathlib
 5
 6import meshio
 7
 8
 9def get_parser() -> argparse.ArgumentParser:
10    """Return the command-line interface parser."""
11    cli_description = (
12        "Convert mesh files to older VTK ASCII version 2 mesh files required by Fierro with MeshIO API. "
13        "This output is not supported by the MeshIO CLI and must be constructed manually."
14    )
15    parser = argparse.ArgumentParser(description=cli_description)
16    parser.add_argument(
17        "infile",
18        type=pathlib.Path,
19        help="Input file to convert",
20    )
21    parser.add_argument(
22        "outfile",
23        type=pathlib.Path,
24        help="Output file to write",
25    )
26    parser.add_argument(
27        "-i",
28        "--input-format",
29        type=str,
30        default=None,
31        help="Explicit input file format (default: %(default)s)",
32    )
33    return parser
34
35
36def main() -> None:
37    """Convert VTK mesh files to the older VTK ASCII version 2 files required by Fierro."""
38    parser = get_parser()
39    args = parser.parse_args()
40
41    infile = pathlib.Path(args.infile).resolve()
42    mesh = meshio.read(infile, file_format=args.input_format)
43
44    # Ensure input mesh structure meets Fierro criteria
45    if len(mesh.cells) != 1:
46        raise ValueError("More than one kind of cell type present")
47    if mesh.cells[0].dim == 1:
48        raise ValueError("Input file is 1D, only 2D/3D work")
49    if mesh.cells[0].type != "hexahedron" and mesh.cells[0].type != "quad":
50        raise ValueError("Cell type is not quad/hexahedron")
51
52    points = mesh.points
53    npoints = points.shape[0]
54
55    ndim = mesh.cells[0].dim
56    cells = mesh.cells[0].data
57    ncells = cells.shape[0]
58    pts_per_cell = cells.shape[1]
59
60    outfile = pathlib.Path(args.outfile).resolve()
61    with outfile.open(mode="w") as mesh_out:
62        mesh_out.write("# vtk DataFile Version 2.0\n")
63        mesh_out.write("meshio converted to Fierro VTK\n")
64        mesh_out.write("ASCII\n")
65        mesh_out.write("DATASET UNSTRUCTURED_GRID\n")
66        # Write points
67        mesh_out.write(f"POINTS {npoints} double\n")
68        for n in range(npoints):
69            for i in range(ndim):
70                mesh_out.write(f"{points[n, i]} ")
71            mesh_out.write("\n")
72        mesh_out.write("\n")
73
74        # Write cells
75        mesh_out.write(f"CELLS {ncells} {ncells * pts_per_cell + ncells}\n")
76        for n in range(ncells):
77            mesh_out.write(f"{pts_per_cell} ")
78            for i in range(pts_per_cell):
79                mesh_out.write(f"{cells[n, i]} ")
80            mesh_out.write("\n")
81        mesh_out.write("\n")
82        mesh_out.write(f"CELL_TYPES {ncells}\n")
83        if ndim == 2:
84            for _n in range(ncells):
85                mesh_out.write("9\n")
86        else:
87            for _n in range(ncells):
88                mesh_out.write("12\n")
89
90
91if __name__ == "__main__":
92    main()

Fierro Input File(s)#

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

waves-tutorials/tutorial_cubit_fierro/modsim_package/fierro/cube_compression.yaml

 1num_dims: 3
 2input_options:
 3    mesh_file_format: vtk
 4    mesh_file_name: cube_mesh.vtk
 5    element_type: hex8
 6    zero_index_base: true
 7
 8output_options:
 9  output_fields:
10    - displacement
11    - strain
12
13materials:
14  - id: 0
15    elastic_modulus: 100
16    poisson_ratio: 0.3
17    density: 0.27000e-08
18    initial_temperature: 293
19
20fea_module_parameters:
21  - type: Elasticity
22    material_id: 0
23    boundary_conditions:
24      - surface:
25          type: y_plane
26          # TODO: replace with element set "bottom"
27          plane_position: 0.0
28        type: displacement
29        value: 0.0
30
31      - surface:
32          type: y_plane
33          # TODO: replace with element set "top", or at least the "height" parameter
34          plane_position: 1.0
35        type: displacement
36        # TODO: replace with "displacement" parameter
37        value: -0.01

SConstruct#

Note that Fierro differs from other solvers in the tutorials. Fierro is deployed as a Conda package and is available in the launching Conda environment. It is still good practice to check if the executable is available and provide helpful feedback to developers about the excutable status and workflow configuration.

The structure has changed enough from the core tutorials that a diff view is not as useful. Instead the contents of the SConstruct file is duplicated below.

waves-tutorials/tutorial_cubit/SConstruct

#! /usr/bin/env python
"""Configure the WAVES Cubit+Abaqus/Fierro/Sierra tutorial."""

import os
import pathlib
import subprocess
import sys
import typing

import waves

# Comments used in tutorial code snippets: marker-1

# Accept command line options with fall back default values
AddOption(
    "--build-dir",
    dest="variant_dir_base",
    default="build",
    nargs=1,
    type="string",
    action="store",
    metavar="DIR",
    help="SCons build (variant) root directory. Relative or absolute path. (default: '%default')",
)
AddOption(
    "--unconditional-build",
    dest="unconditional_build",
    default=False,
    action="store_true",
    help="Boolean flag to force building of conditionally ignored targets. (default: '%default')",
)
AddOption(
    "--print-build-failures",
    dest="print_build_failures",
    default=False,
    action="store_true",
    help="Print task *.stdout target file(s) on build failures. (default: '%default')",
)
# Python optparse appends to the default list instead of overriding. Must implement default/override ourselves.
default_abaqus_commands = [
    "/apps/abaqus/Commands/abq2024",
    "/usr/projects/ea/abaqus/Commands/abq2024",
    "abq2024",
    "abaqus",
]
AddOption(
    "--abaqus-command",
    dest="abaqus_command",
    nargs=1,
    type="string",
    action="append",
    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

# Inherit user's full environment and set project variables
env = waves.scons_extensions.WAVESEnvironment(
    ENV=os.environ.copy(),
    variant_dir_base=pathlib.Path(GetOption("variant_dir_base")),
    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
env.PrintBuildFailures(print_stdout=env["print_build_failures"])

# Comments used in tutorial code snippets: marker-3

# Find required programs for conditional target ignoring and absolute path for use in target actions
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

# Set project internal variables and variable substitution dictionaries
project_name = "WAVES-TUTORIAL"
version = "0.1.0"
project_dir = pathlib.Path(Dir(".").abspath)
project_variables = {
    "project_name": project_name,
    "project_dir": project_dir,
    "version": version,
}
for key, value in project_variables.items():
    env[key] = value

# 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 = [
    "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, "env_sierra": env_sierra},
        duplicate=False,
    )

# Comments used in tutorial code snippets: marker-7

# Add default target list to help message
env.Default()  # Empty defaults list to avoid building all simulation targets by default
# Add aliases to help message so users know what build target options are available
# This must come *after* all expected Alias definitions and SConscript files.
env.ProjectHelp()

# Comments used in tutorial code snippets: marker-8

Build Targets#

  1. Build the new targets

$ pwd
/path/to/waves-tutorials/tutorial_cubit
$ scons fierro
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 fierro-parallel-implicit program exists.../projects/aea_compute/waves-env/bin/fierro-parallel-implicit
Sourcing the shell environment with command 'module use /projects/aea_compute/modulefiles && module load sierra' ...
Checking whether sierra program exists.../projects/sierra/sierra5193/install/tools/sntools/engine/sierra
scons: done reading SConscript files.
scons: Building targets ...
Copy("build/fierro/cube_compression.yaml", "modsim_package/fierro/cube_compression.yaml")
Copy("build/fierro/elasticity3D.xml", "modsim_package/fierro/elasticity3D.xml")
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro && python
/home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/cube_geometry.py >
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_geometry.cub.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro && python
/home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/cube_partition.py >
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_partition.cub.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro && python
/home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/cube_mesh.py --element-type HEX
--solver sierra > /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_mesh.g.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro && python
/home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/fierro/convert_to_vtk2ascii.py
--input-format=exodus /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_mesh.g
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_mesh.vtk >
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_mesh.vtk.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro && mpirun -np 1
fierro-parallel-implicit
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_compression.yaml >
/home/roppenheimer/waves-tutorials/tutorial_cubit/build/fierro/cube_compression.stdout 2>&1
scons: done building targets.

Output Files#

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

$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ tree build/fierro/
build/fierro/
|-- TecplotTO0.dat
|-- TecplotTO_undeformed0.dat
|-- cube_compression.stdout
|-- cube_compression.yaml
|-- cube_geometry.cub
|-- cube_geometry.cub.stdout
|-- cube_mesh.cub
|-- cube_mesh.g
|-- cube_mesh.g.stdout
|-- cube_mesh.vtk
|-- cube_mesh.vtk.stdout
|-- cube_partition.cub
|-- cube_partition.cub.stdout
`-- elasticity3D.xml

0 directories, 14 files