Overview

Using Macros in Input Files

A macro is a mechanism to abstract complex input file sequences into (parameterized) tokens. In its simplest form, a macro provides a way to substitute a code snippet from an input file:

<macro snippet>
    line1
    line2
    line3
 </macro>

In this example, every occurrence of the code named snippet in the input file will now be replaced by the three lines defined between the <macro> and </macro> tags.

For example, you could define a macro to set up a laser pulse like this:

<macro  myFluid>

  equations = [euler]

  <Equation euler>
    kind = eulerEqn
    gasGamma = GAMMA
  </Equation>
</macro>

You could then call your myLaser macro within the input file like this:

<Updater hyper>
  kind = classicMuscl1d
  onGrid = domain
  ...

  myFluid

</Updater>

The USim engine (USim) will expand the input file use of your macro into:

<Updater hyper>
  kind = classicMuscl1d
  onGrid = domain
  ...

  equations = [euler]

  <Equation euler>
    kind = eulerEqn
    gasGamma = GAMMA
  </Equation>

</Updater>

Importing Local Macros

It is also possible to define a macro file, and provided that it is in the same directory as your input file, import it. This is useful when writing one custom macro that will be used over multiple simulations. The macro must have a .mac extension on it to be imported as a local macro. To extend the example above, say the macro myLaser is in the file Lasers.mac, the input file would look like this:

$ import fluidModels.mac

 <Updater hyper>
   kind = classicMuscl1d
   onGrid = domain
   ...

   myFluid

 </Updater>

USim will expand the input file use of your macro into:

 <Updater hyper>
  kind = classicMuscl1d
  onGrid = domain
  ...

  equations = [euler]

  <Equation euler>
    kind = eulerEqn
    gasGamma = GAMMA
  </Equation>

</Updater>

The macro definition would remain the exact same. As long as the macro file is imported properly, it is just like having it defined explicitly in the input file.

Macro Parameters

Macros can take parameters, allowing variables to be passed into and used by the macro. Parameters are listed in parentheses after the macro name in the macro declaration, as in this example:

<macro finiteVolumeData(name, grid, components, write)>
  <DataStruct name>
    kind = nodalArray
    onGrid = grid
    numComponents = components
    numNodes = 1
    writeOut = write
  </DataStruct>
</macro>

Once a macro is defined, it can be used by calling it and providing values or symbols for the parameters. The macro will substitute the parameter values into the body provided. Calling the example above with parameters defined like this:

finiteVolumeData(density, domain, 1, true)

will create the following code fragment in the processed input file:

<DataStruct name>
  kind = nodalArray
  onGrid = grid
  numComponents = components
  numNodes = 1
  writeOut = write
</DataStruct>

Note

The parameter substitution happened in the scope of the caller. Parameters do not have scope outside of the macro in which they are defined.

Macro Overloading

As with symbols, macros can be overloaded within a scope. The particular instance of a macro that is used is determined by the number of parameters provided at the time of instantiation. This enables the user to write macros with different levels of parameterization:

<macro circle(x0, y0, r)>
     r^2 - ((x-x0)^2 + (y-y0)^2)
</macro>
<macro circle(r)>
     circle(0, 0, r)
</macro>

Looking in the example above, whenever the macro circle is used with a single parameter, it creates a circle around the origin; if you use the macro with 3 parameters, you can specify the center of the circle.

The macro substitution does not occur until the macro instantiation is actually made. This means that you do not have to define the 3-parameter circle prior to defining the 1-parameter circle, even though the 1-parameter circle refers to the 3-parameter circle. It is only necessary that the first time the 1-parameter circle is instantiated, that 3-parameter circle has already been defined, otherwise you will receive an error.

Defining Functions Using Macros

Macros can be particularly useful for defining complex mathematical expressions, such as defining functions in expr lists.

Consider a macro that should simplify the setup of a Gaussian. One could define the following macro:

<macro badGauss(A, x, sigma)>
     A * exp(-x^2/sigma)
</macro>

While this is a legitimate macro, an instantiation of the macro via:

badGauss(A0+5, x-3, 2*sigma)

will result in:

A0+5*exp(-x+3^3/2*sigma)

which is probably not the expected result. One alternative is to put parentheses around the parameters whenever they are used in the macro.

<macro betterGauss(A, x, sigma)>
     ((A) * exp(-(x)^2/(sigma)))
</macro>

This will ensure that the expressions in parameters will not cause any unexpected side effects. The downside of this approach, however, is that the macro text is hard to read due to all the parentheses. To overcome this issue, txpp provides a mechanism to automatically introduce the parentheses around arguments by using a function block

<function goodGauss(A, x, sigma)>
     A * exp(-x^2/sigma)
</function>

The previous example will produce the same output as the badGauss macro, but without requiring the additional parentheses in the macro text.

Importing Files

USim allows input files to be split into individual files, thus enabling macros to be encapsulated into separate libraries. For example, physical constant definitions or commonly-used geometry setups can be stored in files that can then be used by many USim simulations. Input files can be nested to arbitrary depth.

Files are imported via the import keyword:

$ import FILENAME

where FILENAME represents the name of the file to be included. txpp applies the standard rules for token substitution to any tokens after the import token. Quotes around the filename are optional and computed filenames are possible.

Conditionals

The USim preprocessor includes both flow control and conditional statements, similar to other scripting languages. These features allow the user a great deal of flexibility when creating input files.

A conditional takes either the form:

$ if COND
      ...
$ endif

or

$ if COND
      ...
$ else
      ...
$ endif

Conditionals can be arbitrarily nested. All the tokens following the if token are interpreted following the expression evaluation procedure (see above) and if they evaluate to true, the text following the if statement is inserted into the output. If the conditional statement evaluates to false, the text after the else is inserted (if present). Note that true and false in preprocessor macros are evaluated by Python – in addition to evaluating conditional statements such as x == 1, other tokens can be evaluated. The most common use of this is using 0 for false and 1 for true. Empty strings are also evaluated to false. For more detailed information, consult the Python documentation.

Example Conditional Statements

$ if TYPE == "MHD"
$    numComponents = 9
$ else
$    numComponents = 5
$ endif

A conditional statement can also use Boolean operators:

$ A = 0
$ B = 0
$ C = 1
#
# Below, D1 is 1 if A, B, or C are non-zero. Otherwise D1=0:
$ D1 = (A) or (B) or (C)
#
# Below, D2 is 1 if A is non-zero or if both B and C are non-zero. Otherwise D2=0:
$ D2 = (A) or ( (B) and (C) )
#
# This can be also be written as an if statement:
$ if (A) or ( (B) and (C) )
$    D3 = 1
$ else $
$    D3 = 0
$ endif

Repetition

For repeated execution, USim provides while loops; these take the form:

$ while COND
    .
    .
    .
$ endwhile

which repeatedly inserts the loop body into the output. For example, to create 10 stacked circles using the circle macro from above, you could use:

$ n = 10
$ while n > 0 circle(n)
$   n = n - 1
$ endwhile

Recursion

Macros can be called recursively. E.g. the following computes the Fibonacci numbers:

<macro fib(a)>
    $ if a < 2
        a
    $ else
        fib(a-1)+fib(a-2)
    $ endif
</macro>
fib(7)

Note

There is nothing preventing you from creating infinitely recursive macros; if terminal conditions are not given for the recursion, infinite loops can occur.

Symbol Definition on the Command Line

txpp allows symbols to be defined on the command line. These definitions override any symbol definitions in the outer-most (global) scope. This allows you to set a default value inside an input file that can then be overridden on the command line if needed.

For example, if the following is in the outermost scope of the input file (outside of any blocks or macros):

$ X = 3
X

Then this will result in a line containing 3 in the output. However, if you were to invoke txpp via:

txpp.py -DX=4

then this will result in a line 4.

However, if you were to define X inside a block (not in the global scope), such as:

<block foo>
    $ X = 3
    X
</block>

then X will always be 3, no matter what value for X is specified on the command line.

Requires

When writing reusable macros, best practices compel macro authors to help ensure that the user can be prevented from making obvious mistakes. One such mechanism is the requires directive, which terminates translation if one or more symbols are not defined at the time. This allows users to write macros that depend on symbols that are not passed as parameters. For example, the following code snippet will not be processed if the symbol NDIM has not been previously defined:

<macro circle(r)>
    $requires NDIM
    $if NDIM == 2 r^2 - x^2 - y^2
    $endif
    $if NDIM == 3 r^2 - x^2 - y^2 - z^2
    $endif
</macro>

String Concatenation

One task that is encountered often during the simulation process is naming groups of similar blocks, e.g. similar species. Macros can allow us to concatenate strings to make this process more clean and simple. However, based on the white-spacing rules, strings will always be concatenated with a space between them. For example,

$a = hello
$b = world
a b
will result in
hello world

However, we can get around this rule to get the desired output with the following:

<macro  concat(a, b)>
    $ tmp = 'a tmp b'
</macro>

Now when calling

concat(hello, world)

the result will be:

helloworld

The first line appends a single quote to a and stores the result in tmp. The next line then puts the token a together with the token b. As they are now no longer two strings, they will be concatenated without a space. The final evaluation of the resulting string then removes the quotes around it, resulting in the desired output.

Now that we have examined macros in an overview, we are ready for Introduction.