3.8. Advanced Setup: Python Scripting¶
In addition to the many settings which can be typed into the main JSON file, users can create Python scripts and/or modules which can either add settings to what’s in the JSON file or change settings depending on the run matrix. For example, a complex run configuration with 100 or more databook components could define them in a Python loop rather than with many lines of ASCII text in the JSON file.
Use of Python can be done in two main ways: linking to modules via certain
options within the JSON file that point to Python modules and functions or
executing scripts which modify the settings via the command-line option -x
.
The JSON options that result in the use of Python functions are PythonPath,
Modules, InitFunction, and CaseFunction.
The PythonPath key is an option simply appends folder names to the environment
variable $PYTHONPATH
so that .py
files in that folder can be directly
imported. Next, Modules simply gives a list of Python files to import as
modules whenever the JSON file is read. Once these modules have been imported,
the InitFunction specifies one or more functions that is called as soon as
pyCart is done interpreting the JSON file. Thus InitFunction is usually most
useful to determine settings that are universal to the whole run matrix but for
whatever reason are easier to program in Python than write out in JSON text.
The last option, CaseFunction, specifies one or more functions to call before
submitting a new case (or running the --apply
command). These functions
take the case number as an argument and can be used to change any setting
depending on what the conditions for that case are.
3.8.1. Using Scripts to Modify or Add Settings¶
Probably the simplest but least reliable method to move some of the work of
creating the settings from JSON to Python is the -x
option. When a
command-line call sees -x SCRIPT
, it first reads the relevant JSON file and
then uses execfile()
to execute that script.
Consider the following example.
$ pyfun -f poweroff.json -x poweroff.py -x report.py
When the executable script pyfun
sees this command, it first loads the
settings from poweroff.json
and then runs the scripts
poweroff.py
and report.py
(in that order) using
execfile()
. To do the same thing with a manually-created script, the
following syntax would work.
# Load relevant module import cape.pyfun # Read JSON file cntl = cape.pyfun.Cntl('poweroff.json') # Apply scripts execfile('poweroff.py') execfile('report.py')
As the example somewhat indirectly shows (at least to those familiar with
execfile()
), the global variable fun3d is accessible within the two
.py
scripts, and modifications to the settings must reference the variable
fun3d. For example, if the goal of poweroff.py
is to set some extra
data book components, the following example might work.
# List of components to add comps = ["nozzle1', "nozzle2", "nozzle3", "attachment"] # Add them as default force and moment components cntl.opts["DataBook"]["Components"] += comps # Loop through four boosters for k in range(4): # Name of booster name = "booster%s" % (k+1) # Name of line load component comp = "%s_ll" % name # Clocking angle so that *CN* points away from core rocket clock = k*90.0 # Add the component cntl.opts["DataBook"]["Components"].append(comp) # Add the definition cntl.opts["DataBook"][comp] = { "Type": "LineLoad", "CompID": name, "Transformations": [{"Type": "Euler321", "phi":clock}] }
As this example might make clear, these scripting capabilities have a tendency
to require or at least benefit from knowledge of the pyCart API. However, while
API functions may be useful for generating these scripts, all settings can be
accessed as if they were in a standard Python dict
. The JSON file is
basically read in as a dictionary that is saved in fun3d.opts. For example,
the "Report"
section of the JSON file becomes fun3d.opts["Report"]
, and
the Report Subfigures subsection is
fun3d.opts["Report"]["Subfigures"]
, etc.
Finally, because this -x
command uses global variables, the scripts must
use the correct variable name for the global settings handle, which is
cntl
.
3.8.2. Global Settings from Python Modules¶
A more reliable method for altering settings from Python instead of JSON
(reliable in the sense that users might forget to add all the correct -x
options while InitFunction contains the proper information right within the
JSON file) is to use initialization functions.
In a way, using modules instead of scripts is slightly more work than using
scripts with the -x
option because it requires creating a module (which is
similar to creating a script) and also setting a few options within the JSON
file.
These functions are controlled in the JSON file using the global
"InitFunction"
setting. Consider the following example from a master JSON
input file.
"PythonPath": ["tools"], "Modules": ["tfm"], "InitFunction": ["tfm.InitArchive"],
These options import a module called tfm
, which may be defined in the
file tools/tfm.py
or tools/tfm/__init__.py
. The function
tfm.InitArchive()
is called immediately after the JSON file is
interpreted, and it can be used to change the settings otherwise specified in
the JSON file.
Example contents of the tfm
module defined in tools/tfm.py
are
below:
# System modules import os # Define an initialization function def InitArchive(cntl): """Change archive folder to appropriate value for calling user""" # Set the *ArchiveFolder* option using an environment variable cntl.opts.set_ArchiveFolder(os.path.join('/u/', os.environ['USER'], 'sls', '10008', 'f3_tfm'))
This function is not terribly complex but accomplishes a task that cannot be performed directly in the JSON file. The example is useful under the assumption that multiple users are running cases from the same input file, and this allows each user to set the archive location to their own home folder even while using identical input files.
The input to the InitFunction is the cape.cntl.Cntl
,
cape.pycart.cntl.Cntl
, cape.pyfun.cntl.Cntl
, etc. global
object that contains all of the settings from the JSON file and an interface to
act on them. The JSON settings are stored within cntl.opts.
The InitFunction can be used to accomplish many more complex tasks. For example you may have many different databook components that have their own definitions, which may or may not be simple. It is usually possible to define these within the JSON file, but it may require many lines of repetitive text. The InitFunction allows the user define these components within a loop using Python code and the Cape API.
3.8.3. Special Settings for Individual Runs from Python Modules¶
A related capability to the InitFunction is the so-called CaseFunction. This specifies one or more Python functions to be executed from a user-defined module(s) after all other setup tasks for a case have been performed. This allows the user to customize CFD settings or any other aspect of the inputs in ways that might not be possible with other Cape capabilities. In short, this can be used as a method of last resort if the environment does not support the level of customization required by the user.
Some common examples of CaseFunction include:
Specifying a different CFL number or some other CFD input as a function of freestream Mach number or angle of attack
Using different meshes according to Mach number or, for example, a control surface deflection setting
Adding an extra Label or tag input variable that allows the user to select from various options using the run matrix
Translating and/or rotating part of the mesh according to values in the run matrix (although this is typically better supported with the TriFunction, translation, rotation, or ConfigFunction keys)
Defining a run matrix variable and using it to turn on/off powered boundary conditions
Creating a function to alter the PBS setting s for each case
An example input from the JSON file is shown below.
"PythonPath": ["tools"], "Modules": ["tfm", "freeair"], "InitFunction": ["tfm.InitArchive"], "CaseFunction": ["freeair.ApplyLabel", "freeair.ApplyTag"]
This instructs Cape to run the functions freeair.ApplyLabel()
and then
freeair.ApplyTag()
after the case has been mostly set up but CFD input
files have not been written yet. Some of the Python syntax for an example for
FUN3D is shown below.
# Filter options based on the *Label* trajectory key def ApplyLabel(cntl, i): # Get nahdle to FUN3D namelist f3d = cntl.opts["Fun3D"] # Key section names nsp = "nonlinear_solver_parameters" lsp = "linear_solver_parameers" ifm = "inviscid_flux_method" # Get *Label* value from the run matrix for case *i* lbl = cntl.x.Label[i] # Select default label if empty if lbl: # Status update print(" Using Label '%s'" % lbl) else: # Check Mach number for this case mach = cntl.x.mach[i] # Filter the default value based on Mach if mach < 1.4: # Select label to run uRANS lbl = 'd' else: # Different label to run RANS lbl = 'b' # Actually change the settings ...
As can be seen from this example, the inputs to a CaseFunction are the
overall control object (cape.pycart.cntl.Cntl
,
cape.pyover.cntl.Cntl
, or cape.pyfun.cntl.Cntl
) and the case
number i.