.. _cape-scripting: 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. .. _cape-scripting-x: 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 :func:`execfile` to execute that script. Consider the following example. .. code-block:: bash $ pyfun -f poweroff.json -x poweroff.py -x report.py When the executable script ``pyfun`` sees this command, it first loads the settings from :file:`poweroff.json` and then runs the scripts :file:`poweroff.py` and :file:`report.py` (in that order) using :func:`execfile`. To do the same thing with a manually-created script, the following syntax would work. .. code-block:: python # 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 :func:`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 :file:`poweroff.py` is to set some extra data book components, the following example might work. .. code-block:: python # 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 :class:`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``. .. _cape-scripting-InitFunction: 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. .. code-block:: javascript "PythonPath": ["tools"], "Modules": ["tfm"], "InitFunction": ["tfm.InitArchive"], These options import a module called :mod:`tfm`, which may be defined in the file :file:`tools/tfm.py` or :file:`tools/tfm/__init__.py`. The function :func:`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 :mod:`tfm` module defined in :file:`tools/tfm.py` are below: .. code-block:: python # 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 :class:`cape.cntl.Cntl`, :class:`cape.pycart.cntl.Cntl`, :class:`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. .. _cape-scripting-CaseFunction: 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. .. code-block:: javascript "PythonPath": ["tools"], "Modules": ["tfm", "freeair"], "InitFunction": ["tfm.InitArchive"], "CaseFunction": ["freeair.ApplyLabel", "freeair.ApplyTag"] This instructs Cape to run the functions :func:`freeair.ApplyLabel` and then :func:`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. .. code-block:: python # 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 (:class:`cape.pycart.cntl.Cntl`, :class:`cape.pyover.cntl.Cntl`, or :class:`cape.pyfun.cntl.Cntl`) and the case number *i*.