A FACETS Tutorial: Input file structure and input-file-driven development

We will go through the steps to setup a simple FACETS simulation. This simulation does not do anything in particular but is designed to demonstrate FACETS input file structure and the input-file-driven developement stratergy that should be emoployed in developing new features.

Throughout, one must keep in mind that FACETS is like a collection of lego blocks: combining various elements from the framework allows one to put together rather complex simulations. Also, adding new features so that they can be used with existing features is relatively easy.

Preamble

For this tutorial we will use a pre-installed serial FACETS executable. Lets call our input file first-tut.pre and create the following shell script to run the simulation::

  INPUT_FILE=first-tut
  TXPP=$HOME/software/facets/bin/txpp.py
  FACETS=$HOME/software/facets/bin/facetser
  
  # run the pre-processor
  cmd="$TXPP $INPUT_FILE.pre"
  echo $cmd
  $cmd

  # run the simulation
  cmd="$FACETS -i $INPUT_FILE.in"
  echo $cmd
  $cmd

You may need to change the path to your install of FACETS. For example, on Tech-X clusters where the code is built every night and installed in the /volatile directory, you will need to do::

  INPUT_FILE=first-tut
  TXPP=/volatile/facets/bin/txpp.py
  FACETS=/volatile/facets/bin/facetsser
  
  # run the pre-processor
  cmd="$TXPP $INPUT_FILE.pre"
  echo $cmd
  $cmd

  # run the simulation
  cmd="$FACETS -i $INPUT_FILE.in"
  echo $cmd
  $cmd

Create this shell-script, call it first-tut.sh, and make it executable::

  chmod u+x first-tut.sh

Now all we need to do it run it::

  ./first-tut.sh

Also, download Visit 2.0 executlable from `this < https://wci.llnl.gov/codes/visit/executables.html>`_ website. We will launch Visit using the command line.

Step 1: The Simplest Input File

FACETS input files look like XML files. Input files can contain nested blocks, each block can contain double, integer and string values. In addition, they can also contain vectors of these values. It is very important to remember that if you want to specify a double in the input file you *must not* omit the decimal point. Otherwise the number will be interpreted as an integer. Similarly, if you want an array of doubles, the *first element* must have a decimal point. Hence, for example you need to do::

  value = 1.0
  listOfNumbers = [1.0, 2.0, 3.0]

The simplest input file given below. It basically does nothing but starts FACETS top-loop and quits.

  # start and end times
  tStart = 0.0
  tEnd = 0.2
  # number of frames to write
  numFrames = 2
  # initial time-step to use
  initDt = 0.01
  verbosity = debug  

  # top level component
  <Component facets>
    kind = updaterComponent

  </Component>

All input files need a start and end time for the simulation, specified as tStart and tEnd. The numFrames variable tells FACETS how many frames of data to write. FACETS will alway write the initial frame and then will write additional numFrames frames. Each frame is an HDF5 file named same as the input file with numbers appended to indicate frame number. For example, first-tut_0.h5, first-tut_1.h5 and first-tut_2.h5 will be created by the above simulation.

The initial time-step is specified in initDt. This can be set to whatever you want as FACETS will simply adjust this depending on the physics included in the simulation.

In each simulation there must be *exactly one* top-level Component block. In the above example it is called facets. You can name it anything you want and this name will be reflected in the output HDF5 files. Many blocks take a special field called the kind field. This field tells FACETS what kind of Component (in this case) to create. For now, we will use the standard updaterComponent component kind.

When we run this input file it will create a set of output HDF5 files and log files::

  ls *.h5 *.log
  first-tut_0.h5 first-tut_0.log first-tut_1.h5 first-tut_2.h5

We can see what is written in the HDF5 by using the h5ls utility::

  h5ls -lr first-tut_0.h5
  /                        Group
  /facets                  Group
  /runInfo                 Group
  /timeData                Group

FACETS writes 3 HDF5 groups: /facets, /runInfo and /timeData. You can see what is in these groups using h5dump::

  h5dump -g /timeData first-tut_0.h5

This simulation has not written any data as we have not allocated any memory or created a grid. We will do this in the next step.

Step 2: Adding a grid and an array

To do anything useful we need to create a grid and allocate some data on it. We will use a rectangular grid for this simulation and name it domain.::

  <Grid domain>
    kind = cart2D
    lower = [0.0, 0.0]
    upper = [1.5, 1.0]
    cells = [200, 100]
  </Grid>

This will create a :math:200\times 100 2D Cartesian grid spanning the physical space :math:(0.0, 1.5) \times (0.0, 1.0). Other Grid kinds also exist for creating body-fitted grids and unstructured grids.

Next, we will allocate some data on this grid::

  <DataStruct q>
    kind = distArray2D
    onGrid = domain
    guard = [2, 2]
    numComponents = 5
  </DataStruct>

This block will create a 2D "distributed" array called "q". A distributed arrat is one that is decomposed using MPI when run in parallel. Note we have to specify the name of the grid the data lives on using the onGrid field. The field guard tell FACETS to use 2 ghost cells on lower and upper sides of the domain. The field numComponents tells FACETS that we wish to store 5 components (density, momentum density and energy density) in this array.

Now our input file should look like::

  # start and end times
  tStart = 0.0
  tEnd = 0.2
  # number of frames to write
  numFrames = 2
  # initial time-step to use
  initDt = 0.01
  verbosity = debug

  # top level component
  <Component facets>
    kind = updaterComponent

    <Grid domain>
      kind = cart2D
      lower = [0.0, 0.0]
      upper = [1.5, 1.0]
      cells = [200, 100]
    </Grid>

    <DataStruct q>
      kind = distArray2D
      onGrid = domain
      guard = [2, 2]
      numComponents = 5
    </DataStruct>

  </Component>

We can run this input file in the usual way. The same HDF5 files will be produced. However, now the HDF5 files will have the array "q" and the grid "domain" written out::

  h5ls -lr first-tut_0.h5
  /                        Group
  /facets                  Group
  /facets/domain           Group
  /facets/q                Dataset {200, 100, 5}
  /runInfo                 Group
  /timeData                Group 

Notice the two additional groups /facets/domain and /facets/q. These store information about the grid and the array we just created. We can do an h5dump to see what is in these::

  h5dump -g /facets/domain first-tut_0.h5

The array "q" will initialized to 0.0 as we have not yet set initial conditions. We will do this in the next step. However, we can now look at this input file using Visit.::

 $HOME/software/visit-2.0.2/bin/visit -o first-tut_0.h5 -assume_format Vs

This will open Visit with the HDF5 file first-tut_0.h5 loaded in. We need the -assume_format Vs option to tell Visit to use the VizSchema? plugin to interpret the HDF5 file. When Visit comes up, "Add" a Pseudocolor color plot of the array component q_0 which is the density.

Step 3: Initializing the array

Now that we have allocated an array we will initialize it. FACETS provides a block called Updater to perform action on data. There are many Updaters and we will use them extensively in constructing a simulation. Updaters are very powerful and can be used to create very complex simulations by combining them carefully. Think of an updater as a generalization of a subroutine or function.

Lets create an updater, named "init" to initialize the "q" array. This updater will use a expressions to initialize all the components of "q".::

   <Updater init>
     kind = initArrayUpdater
     onGrid = domain
     out = [q]

 # initial condition to use
     <Function func>
       kind = exprFunc

	xc = 0.0
	yc = 0.4
	rad = 0.2
	gamma = $5.0/3.0$

 # pre-expression
       preExprs = [ \
	   "r = sqrt((x-xc)^2 + (y-yc)^2)", \
	   "rho = 1.0", \
	   "pr =  if(r>rad, 1.0, 5.0)", \
	   "Er = pr/(gamma-1)"]

 # expressions: one per component to intialize
       exprs = ["rho", "0.0", "0.0", "0.0", "Er"]

     </Function>

   </Updater>

Each updater must have a kind field and an onGrid field. This updater is of kind "initArrayUpdater" and runs on the grid "domain" that we created previously. Updaters usually have in and out fields that specify which datastructures are input and which are output. Other fields and blocks depend on the updater kind. In this updater we are using a Function block to specify a function for use in the initial conditions. The kind of function we are using is the "exprFunction" that uses expressions.

If we simply put the above block in the input file and run the simulation nothing will happen! I.e. the initial conditions will not be applied. The reason is that updaters do not run automatically. We have to specify when the updater will be run using UpdateStep blocks. To do this we add the following block in the input file::

  <UpdaterStep initStep>
    updaters = [init]
  </UpdaterStep>

This tells FACETS that when the initStep updater-step is run to call the updater "init". The final step to have this updater-step run is to add a *single* UpdateSequence block::

  <UpdateSequence sequence>
    startOnly = [initStep]
    loop = []
    writeOnly = []
  </UpdateSequence>

Thats it! When we run the input file the "q" array will be initialized with the updater. Now our complete input file looks like::

  # start and end times
  tStart = 0.0
  tEnd = 0.2
  # number of frames to write
  numFrames = 2
  # initial time-step to use
  initDt = 0.01
  verbosity = debug

  # top level component
  <Component facets>
    kind = updaterComponent

    <Grid domain>
      kind = cart2D
      lower = [0.0, 0.0]
      upper = [1.5, 1.0]
      cells = [200, 100]
    </Grid>

    <DataStruct q>
      kind = distArray2D
      onGrid = domain
      guard = [2, 2]
      numComponents = 5
    </DataStruct>

    <Updater init>
      kind = initArrayUpdater
      onGrid = domain
      out = [q]

  # initial condition to use
      <Function func>
	kind = exprFunc

	xc = 0.0
	yc = 0.4
	rad = 0.2
	gamma = $5.0/3.0$

  # pre-expression
	preExprs = [ \
	    "r = sqrt((x-xc)^2 + (y-yc)^2)", \
	    "rho = 1.0", \
	    "pr =  if(r>rad, 1.0, 5.0)", \
	    "Er = pr/(gamma-1)"]

  # expressions: one per component to intialize
	exprs = ["rho", "0.0", "0.0", "0.0", "Er"]

      </Function>

    </Updater>

    <UpdaterStep initStep>
      updaters = [init]
    </UpdaterStep>

    <UpdateSequence sequence>
      startOnly = [initStep]
      loop = []
      writeOnly = []
    </UpdateSequence>

  </Component>

When this input file is run it will run the "init" updater and initialize the "q" array. We can look at the initial conditions using Visit::

 $HOME/software/visit-2.0.2/bin/visit -o first-tut_0.h5 -assume_format Vs

Step 4: Computing pressure before writing data

If we plot the q_4 variable in Visit we will be plotting the total energy and not the pressure. To compute the pressure we can add another array called pressure to store the pressure::

  <DataStruct pressure>
    kind = distArray2D
    onGrid = domain
    guard = [2, 2]
    numComponents = 1
  </DataStruct>

Next, we add an "exprArrayUpdater" updater to extract the pressure::

  <Updater pressCalc>
    kind = exprArrayUpdater

    onGrid = domain
 # input array
    in = [q]

 # ouput data-structures
    out = [pressure]

    indVars = ["rho", "rhou", "rhov", "rhow", "Er"]

    gamma = GAMMA
    preExprs = ["pr = (Er - 0.5*(rhou^2+rhov^2+rhow^2)/rho)*(gamma-1)"]
    exprs = ["pr"]

  </Updater>

In this updater we assign a name to each of the five components of "q" and use them in expressions to compute the pressure.

Finally, we need to call this updater in an updater-step and add it to the writeOnly list in the update-sequence block::

  <UpdateStep pressureStep>
    updaters = [pressCalc]
  </UpdateStep>

  <UpdateSequence sequence>
    startOnly = [initStep]
    loop = []
    writeOnly = [pressureStep]
  </UpdateSequence>

When we run this simulation an array called "pressure" will also be written out that will store the pressure in the simulation. Note that by putting the "pressCalcStep" in the writeOnly list we are running the "pressCalcStep" only before we write data to file. It is *not* run every time-step. We can now plot this pressure array in visit in the usual way.

The complete input file now looks like::

  # start and end times
  tStart = 0.0
  tEnd = 0.2
  # number of frames to write
  numFrames = 2
  # initial time-step to use
  initDt = 0.01
  verbosity = debug

  # top level component
  <Component facets>
    kind = updaterComponent

    <Grid domain>
      kind = cart2D
      lower = [0.0, 0.0]
      upper = [1.5, 1.0]
      cells = [200, 100]
    </Grid>

    <DataStruct q>
      kind = distArray2D
      onGrid = domain
      guard = [2, 2]
      numComponents = 5
    </DataStruct>

    <DataStruct pressure>
      kind = distArray2D
      onGrid = domain
      guard = [2, 2]
      numComponents = 1
    </DataStruct>

    <Updater init>
      kind = initArrayUpdater
      onGrid = domain
      out = [q]

  # initial condition to use
      <Function func>
	kind = exprFunc

	xc = 0.0
	yc = 0.4
	rad = 0.2
	gamma = $5.0/3.0$

  # pre-expression
	preExprs = [ \
	    "r = sqrt((x-xc)^2 + (y-yc)^2)", \
	    "rho = 1.0", \
	    "pr =  if(r>rad, 1.0, 5.0)", \
	    "Er = pr/(gamma-1)"]

  # expressions: one per component to intialize
	exprs = ["rho", "0.0", "0.0", "0.0", "Er"]

      </Function>

    </Updater>

    <Updater pressCalc>
       kind = exprArrayUpdater

       onGrid = domain
    # input array
       in = [q]

    # ouput data-structures
       out = [pressure]

       indVars = ["rho", "rhou", "rhov", "rhow", "Er"]

       gamma = $5.0/3.0$
       preExprs = ["pr = (Er - 0.5*(rhou^2+rhov^2+rhow^2)/rho)*(gamma-1)"]
       exprs = ["pr"]

     </Updater>

    <UpdaterStep initStep>
      updaters = [init]
    </UpdaterStep>

    <UpdateStep pressureStep>
      updaters = [pressCalc]
    </UpdateStep>

    <UpdateSequence sequence>
      startOnly = [initStep]
      loop = []
      writeOnly = [pressureStep]
    </UpdateSequence>

  </Component>