.. _pycart-ex-thrust:

Demo 8: Powered Rocket and Thrust Vectoring
===========================================

This example explains how to use powered boundary conditions in Cart3D with
pyCart along with a triangulation rotation and intersection as part of a thrust
vector model.  Primarily, this example seeks to introduce how to simulate
rocket engines.  In addition, component rotations and intersections are
demonstrated, and a detailed report including a customized Tecplot layout are
also included.

To get started, clone this repository and run the following easy commands:

    .. code-block:: console

        $ git clone https://github.com/nasa-ddalle/pycart08-thrust.git
        $ cd pycart08-thrust
        $ ./copy-files.py
        $ cd work/

This will copy all of the files into a newly created ``work/`` folder. Follow
the instructions below by entering that ``work/`` folder; the purpose is that
you can easily delete the ``work/`` folder and restart the tutorial at any
time.

To demonstrate these capabilities, we begin with the arrow example of previous
examples as shown in :numref:`fig-pycart-ex08-arrow` and add a stand-alone
engine geometry as shown in :numref:`fig-pycart-ex08-engine`.

    .. _fig-pycart-ex08-arrow:
    .. figure:: arrow01.png
        :width: 3.0 in
        
        Trimmed arrow surface from previous examples
        
    .. _fig-pycart-ex08-engine:
    .. figure:: engine01.png
        :width: 3.0 in
        
        Stand-alone engine block triangulation

These are two water-tight closed geometries so that they can be used with
``intersect``.  In their unmodified positions, the engine intersects the back
plane of the arrow and produces thrust aligned with the centerline of the
arrow.  In this example, we add a pitch angle to the engine so that it is
rotated by an amount set in the run matrix, and this rotation is performed
prior to the intersection.

This example can be found in ``pycart08-thrust``, and as
always the ``pyCart.json`` file in that folder is a good supplement to this
document.  :numref:`fig-pycart-ex08-slice-y0` shows the main product of the
example.

    .. _fig-pycart-ex08-slice-y0:
    .. figure:: slice-y0-mach.png
        :width: 3.5 in
        
        Surface pressure coefficient and :math:`y{=}0` Mach number slice for
        the Mach 1.5 conditions from the example.


Inputs and Run Matrix Description
---------------------------------
Setting up variables to change the thrust of an example is usually contained
within the ``"RunMatrix"`` section of the JSON file.  The following
definitions are used for this example:

    .. code-block:: javascript
    
        "RunMatrix": {
            "Keys": [
                "mach", "alpha", "q", "T",
                "tilt", "CT",
                "config", "Label"
            ],
            "File": "matrix.csv",
            "GroupMesh": false,
            "Definitions": {
                "mach": {
                    "Format": "%0.2f"
                },
                "tilt": {
                    "Type": "rotation",
                    "Value": "float",
                    "Group": false,
                    "Center": [8.0, 0.0, 0.0],
                    "Axis":   [0.0, 1.0, 0.0],
                    "CompID": [
                        "engine_mount",
                        "noz_exterior",
                        "noz_interior",
                        "noz_bc"
                    ],
                    "Abbreviation": "_t",
                    "Format": "%.1f"
                },
                "CT": {
                    "Type": "SurfCT",
                    "Value": "float",
                    "TotalTemperature": 8500.0,
                    "AreaRatio": 4.0,
                    "RefPressure": "freestream",
                    "RefTemperature": "freestream",
                    "RefDynamicPressure": "freestream",
                    "CompID": "noz_bc",
                    "Abbreviation": "T",
                    "Format": "%.1f"
                }
            }
        }
        
In addition to our usual *mach*, *alpha*, *config*, and *Label* parameters that
are part of the standard pyCart setup, we have added a few *RunMatrix>Keys*.
The first two are dynamic pressure (*q*) and freestream static temperature
(*T*).  These are both recognized by pyCart as standard variables, and no
descriptions are needed in the *RunMatrix>Definitions* section.

The next trajectory key is *tilt*, which is defined so that it pitches the
engine block from :numref:`fig-pycart-ex08-engine` by an angle equal to the
value of this variable.  The fact that this is a rotation is set in the *Type*
option within *RunMatrix>Definitions>tilt*.  The center of rotation is set as
``[8.0, 0.0, 0.0]``, which is the center of the back plane of the arrow.  The
value of *Axis* makes this a pitch rotation.  *CompID* is a list of components
that are rotated, which can be either strings or component numbers.  This is a
pretty standard rotation, but users are advised that there are many more
rotation & translation options available.

The last key is *CT*, whose *Type* of ``"SurfCT"`` tells pyCart that the value
of this key is used to set a surface boundary condition with the intent of
setting a nozzle to attain a desired thrust.  The ``"SurfCT"`` and ``"SurfBC"``
types are both targeted at powered boundary conditions, but ``"SurfBC"``
generally targets a desired stagnation pressure while ``"SurfCT"``  targets a
desired thrust.

We should also take this opportunity to discuss the effects of including *q*
and *T*.  Normally, since Cart3D is an inviscid solver, these dimensional
parameters have no effect at all, and the results are truly nondimensional.
However, introducing an engine partially breaks this symmetry to freestream
conditions.  For one thing, a rocket producing an amount of thrust in pounds
will have a different thrust coefficient depending on the freestream dynamic
pressure.  Similarly, a particular temperature at the rocket boundary has
different normalized temperatures for different freestream temperatures.  While
it is possible in pyCart to use a ``"SurfCT"`` key without *q* and *T*, this is
unlikely to be a physically relevant setup.

Going back to the JSON settings for *CT*, we see a *TotalTemperature* of
8500.0, which sets the stagnation temperature at the boundary condition plane
to a constant temperature in degrees Rankine.  If we wanted to set the
*TotalTemperature* relative to the freestream temperature instead of a fixed
dimensional value, we would set *RefTemperature* to ``1.0`` instead of its
``"freestream"`` value.  It is also possible to use the value of another
variable to change the stagnation temperature from case to case by setting the
value of *TotalTemperature* to the name of another trajectory key.  See the
following example for how this could work.

    .. code-block:: javascript
    
        "CT": {
            "Type": "SurfCT",
            "Value": "float",
            "TotalTemperature": "T0",
            ...
        },
        "T0": {
            "Type": "value",
            "Value": "float"
        }
        
We have also set *AreaRatio* here; for Cart3D thrust setup we usually need this
parameter for Cart3D's internal calculation of anticipated thrust.  It is
typically recommended to set the boundary condition on a plane where the Mach
number is 1.0 in Cart3D, but the Mach number on the plane can be set to a
different value using *Mach* within the *Definitions>CT*.  pyCart then uses
this information to calculate the static pressure and density at the boundary
condition plane that should give the corresponding thrust.

While pyCart automatically calculates the surface normal of that plane (since
the velocity has to be set on that plane including its three components), this
simplified thrust calculation is not perfect. In order to get the correct
thrust, there is also a *PressureCalibration* option that can be used to
linearly scale the surface pressure.


.. _pycart-ex08-intersect:

Intersection Process
--------------------
Intersecting closed volumes that each have multiple component IDs marked is a
nontrivial process.  Because ``intersect`` is expecting an input triangulation
in which each component is a water-tight surface with one component, pyCart has
to do some extra preprocessing and postprocessing steps.  To get things to work
properly, we use two separate ``tri`` files and set the following settings in
the JSON.

    .. code-block:: javascript
    
        "Mesh": {
            // Surface triangulation
            "TriFile": ["arrow.tri", "engine.tri"],
            // Extra refinements
            "XLev": [
                {"n": 2, "compID": "noz_bc"},
                {"n": 1, "compID": "noz_interior"}
            ],
            // Extra bounding boxes for adaptation regions
            "BBox": [
                {"n": 8, "compID": "noz_exterior", "xp": 2.5}
            ]
        }
        
The key parameter here is that *Mesh>TriFile* is a list of two files.  As a
result, pyCart assumes that each individual file is a single closed volume.
The *XLev* descriptions specify additional refinements on any cut cells that
intersect specified components, while *BBox* gives rectangular prisms in which
to make a specified number *n* of refinements.

:numref:`fig-pycart-ex08-c-png` shows the original surface triangulation after
rotations but before performing the intersection  operation.  It contains the
same component breakdown as the original input files and is labeled
``Components.c.tri`` in the folder.  pyCart also writes the file
``Components.tri`` which contains the same nodes and triangles but only has
two components, and a visualization is shown in :numref:`fig-pycart-ex08--png`.

    .. _fig-pycart-ex08-c-png:
    .. figure:: Components_c.png
        :width: 3.2 in
        
        Raw self-intersecting surface with original component IDs,
        ``Components.c.tri``
        
    .. _fig-pycart-ex08--png:
    .. figure:: Components.png
        :width: 3.2 in
        
        Self-intersecting surface with one component ID for each closed volume,
        ``Components.tri``
        
Then a call is made to Cart3D's ``intersect`` tool such that the input is
``Components.tri``, and the output is ``Components.o.tri``, which is
shown in :numref:`fig-pycart-ex08-o-png`.
        
    .. _fig-pycart-ex08-o-png:
    .. figure:: Components_o.png
        :width: 3.2 in
        
        Intersected or trimmed surface with one component ID for each original
        closed volume, ``Components.o.tri``
        
In order to get the original components requested by the user, pyCart then
performs an additional step of remapping the component IDs to create
``Components.i.tri``, shown in :numref:`fig-pycart-ex08-i-png`.  Each
triangle has the component ID copied from the closest triangle of
``Components.c.tri``.
        
    .. _fig-pycart-ex08-i-png:
    .. figure:: Components_i.png
        :width: 3.2in
        
        Intersected or trimmed surface with original component IDs mapped,
        ``Components.i.tri``

Results and Report Generation
-----------------------------
The run matrix in ``pycart08-thrust/matrix.csv`` has only one
case, which has a Mach number of 1.5, an angle of attack of 2 degrees.  The
engine is pitched downward 4.5 degrees and a thrust coefficient of 8.5.  A
status while running the case would look something like the following.

    .. code-block:: console
    
        $ pycart -c
        Case Config/Run Directory       Status  Iterations  Que CPU Time 
        ---- -------------------------- ------- ----------- --- --------
        0    poweron/m1.50a2.0_t4.5T8.5 RUN     50/700      .      452.9 
        
        RUN=1, 

:numref:`fig-pycart-ex08-slice-y0-mesh` shows a flow visualization of this case
that is generated using the ``"slice-y0-mesh"`` subfigure from
``pyCart.json``.  (The results of the ``"slice-y0"`` subfigure is shown in
:numref:`fig-pycart-ex08-slice-y0`.)  These figures show some of the more
advanced procedures from customizing a Tecplot layout.

        
    .. _fig-pycart-ex08-slice-y0-mesh:
    .. figure:: slice-y0-mach-mesh.png
        :width: 4in
        
        Surface pressure coefficient (:math:`C_p`) and :math:`y{=}0` Mach
        number slice showing volume mesh
        
The process for this example begins with opening the output flow visualization
files created by Cart3D: ``Components.i.plt`` and ``cutPlanes.plt``.
Actually those files are in the ``adapt03/`` folder in this case, but pyCart
automatically creates symbolic links to the most recent ``plt`` files.

Then, after opening those files, the user should create the desired image and
save it as a layout.  A hidden step necessary for this example is that the user
has to customize the color map for the Mach slice.  Since layout files do not
have ``CREATECOLORMAP`` commands for built-in color maps, there is no color map
in the layout file to edit.  It may be possible without this step, but this
documents one known process.  Simply enter the contour details dialouge in
Tecplot and change one of the colors or slide one of the handles in the color
map interface.  This needs to be performed for both color maps since we are
using separate contours on the surface and the slice.

The JSON description for the two flow visualization subfigures is shown below:

    .. code-block:: javascript
    
        "TecBase": {
            "Type": "Tecplot",
            "FigWidth": 1024,
            "Width": 0.48,
            "Caption": "Surface $C_p$ and $y{=}0$ Mach slice",
            "ContourLevels": [
                {
                    "NContour": 1,
                    "MinLevel": -0.4,
                    "MaxLevel": 1.2,
                    "Delta": 0.1
                },
                {
                    "NContour": 2,
                    "MinLevel": 0.0,
                    "MaxLevel": 4.0,
                    "Delta": 0.1
                }
            ],
            "ColorMaps": [
                {
                    "NContour": 1,
                    "ColorMap": {
                        "-0.4": "blue",
                        "0.0": "white",
                        "1.2": "red"
                    }
                },
                {
                    "NContour": 2,
                    "Constraints": ["mach > 1.25"],
                    "ColorMap": {
                        "0.0": "darkpurple",
                        "1.0": ["#b55fbf", "green"],
                        "$mach": "white",
                        "4.0": "darkorange"
                    }
                }
            ]
        },
        // With mesh
        "slice-y0-mach": {
            "Type": "TecBase",
            "Layout": "slice-y0-mach.lay"
        },
        "slice-y0-mach-mesh": {
            "Type": "TecBase",
            "Layout": "slice-y0-mach-mesh.lay"
        }

The two subfigures share most of their options, so they cascade from a common
subfigure called ``"TecBase"``.  Only the name of the layout file is changed.
However, the two layouts are very similar; we could use the following alternate
definition.

    .. code-block:: javascript
    
        "slice-y0-mach-mesh": {
            "Type": "TecBase",
            "Layout": "slice-y0-mach.lay",
            "Keys": {
                "FIELDLAYERS": {
                    "SHOWMESH": "YES"
                }
            }
            
The minimum and maximum values for the two contour maps are set in the
*ContourLevels* section.  Of course, these fixed values could have just been
set within Tecplot, but this allows for min and max values to depend on the
trajectory keys.

To see how this works, see the more complex *ColorMaps* section. Here we set
the surface pressure map so that ``"blue"`` is at the minimum pressure of
``"-0.4"``, white is at *Cp=0*, and the maximum value is red. This simplifies
the process of getting white to lie on *Cp=0* with an asymmetric range of
values.

The color map for the Mach slice is more complicated.  Here we have set
``"darkpurple"`` at Mach 0, a lighter purple of ``"#b55fbf"`` on the lower side
of Mach 1, ``"green"`` on the upper side of Mach 1.  This list of two colors at
Mach 1 leads to a sharp purple/green divide at the sonic line.  Then we set
``"white"`` as the color for ``"$mach"``; the ``$`` tells pyCart to replace
this with the value of the trajectory key *mach* for this color.  Finally, we
use ``"darkorange"`` for top of the color map.

The result is a very informative color map that clearly identifies subsonic
flow, low supersonic flow, the freestream Mach condition, and high supersonic
flow.  Furthermore, this color map setup, by setting ``"$mach": "white"``, it
applies to a range of conditions.  The color map shown above could lead to
problems if the Mach number is lower than about 1.2, so the actual JSON file
contains three different color map specifications.  Which one gets applied is
determined by the *Constraints* key, which is visible in the code snippet show
above.