Tutorial: Quinoa#

Warning

This tutorial is in a draft outline state. It is provided as reference and will be fleshed out in future releases. Be sure to check back in on this tutorial or watch the Changelog for updates!

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. While the Quinoa team refactors around a breaking change in the input file parser, the Quiona tutorial is not part of the regression suite, but the Quinoa builder unit tests are part of the verification suite. If you run into problems running this tutorial, please contact the Quiona development team for the current status of the input file parser.

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 quinoa-local alias without sbatch submission is part of the regression suite, but neither the sbatch submission nor the quinoa-remote alias is regression tested. The SSH remote excution and sbatch behavior was tested when the tutorial was written and is expected to run correctly. If you run into problems running this tutorial, please contact the WAVES development team.

References#

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
    
  2. Activate the environment

    $ conda activate waves-tutorial-env
    

Some tutorials require additional third-party software that is not available for the Conda package manager. This software must be installed separately and either made available to SConstruct by modifying your system’s PATH or by modifying the SConstruct search paths provided to the waves.scons_extensions.add_program() method.

Directory Structure#

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

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --destination tutorial_quinoa tutorials/tutorial_quinoa
$ ls tutorial_quinoa
box.py flow.lua SConstruct SConscript
  1. Make the new tutorial_quinoa directory the current working directory

$ pwd
/home/roppenheimer/waves-tutorials
$ cd tutorial_quinoa
$ pwd
/home/roppenheimer/waves-tutorials/tutorial_quinoa
$ ls
box.py flow.lua SConstruct SConscript

SConscript#

  1. Review the SConscript file.

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

waves-tutorials/tutorial_quinoa/SConscript

 1#! /usr/bin/env python
 2import pathlib
 3
 4import waves
 5
 6
 7Import("env", "envQuinoa")
 8
 9# TODO: move parameters to a dedicated dictionary file read
10simulation_variables = {
11    "xlength": 1.0,
12    "ylength": 0.5,
13    "zlength": 0.5,
14}
15
16workflow = []
17remote_directory_prefix = env["remote_directory"]
18
19# Geometry/partition/mesh
20# TODO: separate the local and remote workflows into dedicated SConscript files that re-use the cubit workflow as in the
21# Cubit+Abaqus and Cubit+Sierra tutorials
22mesh = env.PythonScript(
23    target=["box.cub", "box.exo"],
24    source=["box.py"],
25    subcommand_options="--output-file ${TARGET.abspath} --xlength ${xlength} --ylength ${ylength} --zlength ${zlength}",
26    **simulation_variables,
27)
28workflow.extend(mesh)
29env.Alias("mesh", mesh)
30
31# SSH remote build on HPC
32remote_directory = remote_directory_prefix / "remote"
33remote_target = env.SSHQuinoaSolver(
34    target=["remote.stdout"],
35    source=["flow.lua", "box.exo"],
36    charmrun="charmrun",
37    inciter="inciter",
38    remote_directory=remote_directory,
39)
40env.Alias("quinoa-remote", remote_target)
41
42# Local build on local server
43local_target = envQuinoa.QuinoaSolver(
44    target=["local.stdout"],
45    source=["flow.lua", "box.exo"],
46    charmrun=envQuinoa["charmrun"],
47    inciter=envQuinoa["inciter"],
48)
49envQuinoa.Alias("quinoa-local", local_target)
50
51if not env["unconditional_build"] and not envQuinoa["inciter"]:
52    print(f"Program 'inciter' was not found in construction environment. Ignoring 'quinoa-local' target(s)")
53    Ignore([".", "quinoa-local"], local_target)

Cubit Journal Files#

  1. Review the box.py Cubit journal file

The Cubit journal file includes a similar 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. Unlike the core tutorials, this journal file only parameterizes the geometry, so intermediate partition and mesh journal files are not as useful and the journal file performs all operations at once.

waves-tutorials/tutorial_quinoa/box.py

 1import sys
 2import argparse
 3import pathlib
 4
 5import cubit
 6
 7
 8def main(output_file, xlength, ylength, zlength):
 9    """Write brick geometry with specified dimensions
10
11    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` and
12        ``.exo`` will be used for the model and mesh files, respectively.
13    :param xlength: box edge length on global x axis
14    :param ylength: box edge length on global y axis
15    :param zlength: box edge length on global z axis
16
17    :returns: writes ``output_file``.cub and ``output_file``.exo
18    """
19    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
20    cubit.cmd("new")
21    cubit.cmd("reset")
22
23    cubit_file = pathlib.Path(output_file).with_suffix(".cub")
24    exodus_file = cubit_file.with_suffix(".exo")
25
26    # Geometry
27    cubit.cmd(f"brick x {xlength} y {ylength} z {zlength}")
28    cubit.cmd(f"move volume 1 x {xlength * 0.5} y {ylength * 0.5} z {zlength * 0.5}")
29    cubit.cmd(f"webcut volume 1 with plane xplane offset {xlength * 0.5} noimprint nomerge")
30    cubit.cmd(f"merge volume 1 2")
31
32    # Sets (partition)
33    cubit.cmd("sideset 1 add surface 8 15")
34    cubit.cmd("sideset 2 add surface 9 16")
35    cubit.cmd("sideset 3 add surface 10 14")
36    cubit.cmd("sideset 4 add surface 4")
37    cubit.cmd("sideset 5 add surface 11 13")
38    cubit.cmd("sideset 6 add surface 6")
39
40    # Mesh
41    cubit.cmd("Trimesher surface gradation 1.05")
42    cubit.cmd("Trimesher volume gradation 1.05")
43
44    cubit.cmd("surface 6 7 8 9 10 11 13 14 15 16 Scheme TriMesh geometry approximation angle 10")
45    cubit.cmd("surface 13 14 15 16  size 0.065")
46    cubit.cmd("surface 4 size 0.09")
47    cubit.cmd("surface 7 size .09")
48    cubit.cmd("surface 8 9 10 11 size 0.45")
49    cubit.cmd("surface 6 size 0.09")
50
51    cubit.cmd("mesh surface 6 7 8 9 10 11 13 14 15 16")
52
53    cubit.cmd("volume 1 2 Scheme Tetmesh proximity layers off geometry approximation angle 10")
54    cubit.cmd("volume 1 2 Tetmesh growth_factor 1.001")
55    cubit.cmd("volume 1 size 0.065")
56    cubit.cmd("volume 2 size 0.065")
57
58    cubit.cmd("mesh volume 1 2")
59    cubit.cmd("block 1 add volume 1 2")
60
61    cubit.cmd("set exodus netcdf4 off")
62    cubit.cmd("set large exodus file on")
63    cubit.cmd(f"export Genesis '{exodus_file}' dimension 3 block 1  overwrite")
64    cubit.cmd(f"save as '{cubit_file}' overwrite")
65
66
67def get_parser():
68    """Return the command line parser"""
69    script_name = pathlib.Path(__file__)
70    prog = f"python {script_name.name} "
71    cli_description = "Write brick geometry ``output_file``.cub and ``output_file``.exo with specified dimensions"
72    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
73    parser.add_argument(
74        "--output-file",
75        type=str,
76        required=True,
77        # fmt: off
78        help="The output file for the Cubit model. Will be stripped of the extension and ``.cub`` and ``.exo`` will "
79             "be used for the model and mesh files, respectively.",
80        # fmt: on
81    )
82    parser.add_argument("--xlength", type=float, required=True, help="box edge length on global x axis")
83    parser.add_argument("--ylength", type=float, required=True, help="box edge length on global y axis")
84    parser.add_argument("--zlength", type=float, required=True, help="box edge length on global z axis")
85    return parser
86
87
88if __name__ == "__main__":
89    parser = get_parser()
90    args = parser.parse_args()
91    main(
92        output_file=args.output_file,
93        xlength=args.xlength,
94        ylength=args.ylength,
95        zlength=args.zlength,
96    )

Quinoa Input File(s)#

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

waves-tutorials/tutorial_quinoa/flow.lua

 1inciter = {
 2
 3  title = "Flow in a simple box" ,
 4
 5  term = 0.11,  -- Max physical time, s
 6  ttyi= 10,  -- TTY output interval
 7  cfl =0.8,
 8  scheme = "alecg",
 9
10  partitioning = "rcb",
11
12  compflow = {
13    physics ="euler", 
14  },
15    -- Units: m,kg,s,
16  ic = {
17    density = 0.912,  -- kg/m^3
18    velocity={ 0.0, 0.0, 0.0 },  -- m/s
19    pressure = 77816.5,  -- Initiate linear
20  },
21
22  material = {
23    {
24      gamma = { 1.4 },
25      cv ={ 717.5 },
26    }
27  },
28
29  bc = { 
30    {
31      farfield = {6},  -- Outlet
32      pressure = 77816.5,
33      density = 0.912,
34      velocity = {0.0, 0.0, 0.0},
35      timedep = { 
36	{
37          sideset = {4}, --Inlet
38	  fn = {  -- t  p  rho  xvel yvel zvel
39            0.0, 77816.5, 0.912, 0.000, 0.000, 0.000,
40            0.1, 77816.5, 0.912, 5.000, 0.000, 0.000,
41           10.0, 77816.5, 0.912, 5.000, 0.000, 0.000,
42	  }
43	}
44      },
45      symmetry = {1,2,3,5},
46    }
47  },
48
49
50  history_output = {
51    interval = 1,
52    point = {
53      {id = "p1", coord = { 0.5, 0.25, 0.25 }},
54    }
55  },
56
57
58  field_output = {
59    time_interval = 0.001,
60    nodevar = {
61      "density",
62      "x-velocity",
63      "y-velocity",
64      "z-velocity",
65      "specific_total_energy",
66      "pressure"
67    }
68  },
69 
70
71  diagnostics = {
72    interval= 250,
73    format  = "scientific",
74    error   =  "l2"
75  }
76
77}

SConstruct#

Note that Quinoa requires a separate construction environment from the launching Conda environment. This is because the Quinoa compiler/linker and openmpi library conflict with the launching Conda environment. You may need to update the Quinoa 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_quinoa/SConstruct

--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_04_simulation_SConstruct
+++ /home/runner/work/waves/waves/build/docs/tutorial_quinoa_SConstruct
@@ -1,7 +1,9 @@
 #! /usr/bin/env python
 
 import os
+import getpass
 import pathlib
+import subprocess
 
 import waves
 
@@ -33,20 +35,19 @@
     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",
+default_cubit_commands = [
+    "/apps/Cubit-16.12/cubit",
+    "/usr/projects/ea/Cubit/Cubit-16.12/cubit",
+    "cubit",
 ]
 AddOption(
-    "--abaqus-command",
-    dest="abaqus_command",
+    "--cubit-command",
+    dest="cubit_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})",
+    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
@@ -57,18 +58,31 @@
     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"])
 
+# Always copy (no sym-links) when duplicating
+env.SetOption("duplicate", "copy")
+
 # 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["SBATCH_PROGRAM"] = env.AddProgram(["sbatch"])
+env["user"] = getpass.getuser()
+
+# Try a preference ordered list of project server-specific environment modulefiles
+quinoa_hpc_modulefile = pathlib.Path("/usr/projects/ea/Quinoa/modules/modulefiles/quinoa")
+quinoa_modulefile_options = [
+    pathlib.Path("/projects/aea_compute/modulefiles/aea-quinoa"),
+    quinoa_hpc_modulefile,
+]
+quinoa_modulefile = next((path for path in quinoa_modulefile_options if path.exists()), None)
 
 # Comments used in tutorial code snippets: marker-4
 
@@ -80,30 +94,53 @@
     "project_name": project_name,
     "project_dir": project_dir,
     "version": version,
+    "remote_directory": pathlib.Path(f"/users/{env['user']}/WAVES-TUTORIAL/tutorial_quinoa"),
 }
 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.Append(
+    BUILDERS={
+        "SSHQuinoaSolver": waves.scons_extensions.ssh_builder_actions(
+            waves.scons_extensions.sbatch_quinoa_builder_factory(
+                environment=f"module use {quinoa_hpc_modulefile.parent} && module load {quinoa_hpc_modulefile.name} &&"
+            ),
+            remote_server="ro-rfe.lanl.gov",
+        )
+    }
+)
+
+# Source the Quinoa environment and store it separately from the Conda/Cubit environment to avoid PATH interference
+if quinoa_modulefile:
+    envQuinoa = waves.scons_extensions.shell_environment(
+        "module unload aea-nightly aea-weekly aea-quarterly aea-release && "
+        f"module use {quinoa_modulefile.parent} && "
+        f"module load {quinoa_modulefile.name}"
+    )
+else:
+    envQuinoa = Environment()
+envQuinoa.AddMethod(waves.scons_extensions.add_program, "AddProgram")
+envQuinoa["inciter"] = envQuinoa.AddProgram(["inciter"])
+envQuinoa["charmrun"] = envQuinoa.AddProgram(["charmrun"])
+
+# Add WAVES builders. If sbatch is found, use it.
+# Should allow the same scons alias to run directly on sstbigbird, but submit as an sbatch job on HPC
+quinoa_builder_factory = waves.scons_extensions.quinoa_builder_factory
+if env["SBATCH_PROGRAM"] is not None:
+    quinoa_builder_factory = waves.scons_extensions.sbatch_quinoa_builder_factory
+envQuinoa.Append(
+    BUILDERS={"QuinoaSolver": quinoa_builder_factory(program=envQuinoa["charmrun"], subcommand=envQuinoa["inciter"])}
+)
 
 # 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",
-]
-for workflow in workflow_configurations:
-    build_dir = env["variant_dir_base"] / workflow
-    SConscript(workflow, variant_dir=build_dir, exports={"env": env}, duplicate=False)
+SConscript(
+    "SConscript", variant_dir=env["variant_dir_base"], exports={"env": env, "envQuinoa": envQuinoa}, duplicate=True
+)
 
 # Comments used in tutorial code snippets: marker-7
 

Note that the Cubit Python file doesn’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 quinoa-local and quinoa-remote workflow configurations will be found by SCons at execution time.

Build Targets#

  1. Build the new targets

$ pwd
/home/roppenheimer/waves-tutorials/tutorial_quinoa
scons: Reading SConscript files ...
Checking whether '/apps/Cubit-16.12/cubit' program exists.../apps/Cubit-16.12/cubit
Checking whether 'cubit' program exists...no
Checking whether 'sbatch' program exists...no
Sourcing the shell environment with command 'module use /projects/aea_compute/modulefiles && module load aea-quinoa' ...
Checking whether 'inciter' program exists.../projects/aea_compute/aea-quinoa/bin/inciter
Checking whether '/users/cclong/QUINOA/quinoa/buildOS/Main/inciter' program exists...no
Checking whether 'charmrun' program exists.../projects/aea_compute/aea-quinoa/bin/charmrun
Checking whether '/users/cclong/QUINOA/quinoa/buildOS/Main/charmrun' program exists...no
scons: done reading SConscript files.
scons: Building targets ...
cd /home/roppenheimer/waves-tutorials/tutorial_quinoa/build && python /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/box.py --output-file /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/box.cub --xlength 1.0 --ylength 0.5 --zlength 0.5 > /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/box.cub.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_quinoa/build && /projects/aea_compute/aea-quinoa/bin/charmrun +p1 /projects/aea_compute/aea-quinoa/bin/inciter --control /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/flow.lua --input /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/box.exo > /home/roppenheimer/waves-tutorials/tutorial_quinoa/build/local.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_quinoa
$ tree build/
build
|-- SConscript
|-- box.cub
|-- box.cub.stdout
|-- box.exo
|-- box.py
|-- diag
|-- flow.lua
|-- flow.q
|-- inciter_input.log
|-- inciter_screen.log
|-- local.stdout
|-- mesh_edge_pdf.0.txt
|-- mesh_ntet_pdf.0.txt
|-- mesh_vol_pdf.0.txt
|-- out.e-s.0.1.0
`-- restart
    |-- MainChares.dat
    |-- RO.dat
    `-- sub0
        |-- Chares_0.dat
        |-- Groups_0.dat
        |-- NodeGroups_0.dat
        `-- arr_0.dat

2 directories, 21 files