Geometry Parameterization

This section will show you how to use DVGeometryMulti to set up a component-based geometry parameterization. This will be the bulk of the work when optimizing with component intersections.

Component FFDs

For the DLR-F6 configuration, we will have two components: the wing and the fuselage. We first need to create FFDs for each component, as shown below.

../_images/intersection_wing_ffd.jpeg

Wing geometry and FFD control points

../_images/intersection_fuse_ffd.jpeg

Fuselage geometry and select FFD control points

The fuselage FFD control points shown here are from only one volume of the entire fuselage FFD. We will define the fuselage design variables on this volume. In general, a component FFD must fully embed the component.

The geometry setup is defined in SETUP/setup_dvgeo.py. We start by defining the path to the FFD files.

    # Get the FFD files
    fuseFFD = os.path.join(inputDir, "ffd", "dlr-f6_fuse.xyz")
    wingFFD = os.path.join(inputDir, "ffd", "dlr-f6_wing.xyz")

Triangulated surfaces

Each component also needs a triangulated surface mesh. Triangulated surfaces are used to perform surface projections, compute intersections, and remesh curves. The triangulated surfaces should be fine enough to accurately capture the surface curvature in areas of interest. As a result, they are often much finer than the CFD surface meshes near component intersections. Instructions for generating triangulated surface meshes using ICEM CFD are outlined in the pySurf docs.

../_images/intersection_wing_tri.jpeg

Wing triangulated surface

../_images/intersection_fuse_tri.jpeg

Fuselage triangulated surface

Similar to the FFD files, we define the path to the triangulated surface files in the setup script.

    # Get the triangulated surface mesh files
    fuseTri = os.path.join(inputDir, "tri", "dlr-f6_fuse.cgns")
    wingTri = os.path.join(inputDir, "tri", "dlr-f6_wing.cgns")

Feature curves

Feature curves are used to preserve the mesh topology around important geometric features. Points on feature curves will remain on the feature curves after deformation. For a wing, we will usually define features curves on the leading and trailing edges, as shown below.

../_images/intersection_feature_curves.jpeg

Wing feature curves before and after deformation

Feature curves must be defined in the CGNS file for the triangulated surface mesh. We collect the names of the relevant curves as the keys for a dictionary called featureCurves that will then be passed to DVGeometryMulti. The values are the marching directions for each curve. For the wing feature curves, we use 2, which means that the curve is remeshed in the positive y-direction. We also define two feature curves on the fuselage symmetry plane. These have a marching direction of None, which means that the entire curve is remeshed.

    # Define feature curves on the wing
    featureCurves = {
        "curve_te_upp": 2,  # upper trailing edge
        "curve_te_low": 2,  # lower trailing edge
        "curve_le": 2,  # leading edge
        "root_skin": None,
        "root_te": None,
    }

We also define a curveEpsDict dictionary containing curve projection tolerances. The keys of the dictionary are the curve names and the values are distances. All points within the specified distance from the curve are considered to be on the curve. In addition to the feature curves, we also define a tolerance for the intersection curve.

    # Define the curve projection tolerances
    curveEpsDict = {
        "curve_te_upp": 0.5e-4,  # upper trailing edge
        "curve_te_low": 0.5e-4,  # lower trailing edge
        "curve_le": 0.5e-4,  # leading edge
        "root_skin": 0.5e-4,
        "root_te": 0.5e-4,
        "intersection": 1.3e-4,
    }

DVGeometryMulti API

We can now define the DVGeometryMulti object that will handle geometric operations during the optimization. First, we use the FFD file paths we defined eariler to instantiate a DVGeometry object for each component FFD. We then instantiate a DVGeometryMulti object.

Note

If you are doing a multipoint problem, you must pass the comm object to DVGeometryMulti, that is, DVGeometryMulti(comm). Failure to do so may result in inaccurate derivatives.

    # Create DVGeometry objects
    DVGeoFuse = DVGeometry(fuseFFD)
    DVGeoWing = DVGeometry(wingFFD)

    # Create the DVGeometryMulti object
    DVGeo = DVGeometryMulti()

To this DVGeometryMulti object, we add the component DVGeometry objects using addComponent. We define the list comps because accessing the entries of the list is less error prone than repeating the same string throughout the code. We also pass the path to the triangulated surface file for each component and a scale factor. Our triangulated surface meshes are defined in millimeters, whereas the CFD mesh is defined in metres. We account for this difference by setting the scale factor equal to 0.001.

    # Define component names
    comps = ["fuse", "wing"]

    # Add components
    DVGeo.addComponent(comps[0], DVGeoFuse, triMesh=fuseTri, scale=0.001)
    DVGeo.addComponent(comps[1], DVGeoWing, triMesh=wingTri, scale=0.001)

Finally, we define the intersection between the wing and fuselage using addIntersection. Here, we use the feature curve dictionaries we defined earlier. You can find descriptions for each parameter in the DVGeometryMulti API documentation.

    # Add intersection
    DVGeo.addIntersection(
        comps[0],
        comps[1],
        dStarA=0.06,
        dStarB=0.15,
        featureCurves=featureCurves,
        project=True,
        includeCurves=True,
        curveEpsDict=curveEpsDict,
    )

Wing design variables

We can define design variables for the wing in the same way as for a wing-only optimization. These design variables must then be added to the component DVGeometry object, not the DVGeometryMulti object. The horizontal and vertical displacement variables are less conventional but follow the same ideas as a more conventional global design variable like twist.

    # Add the reference axis, which we use for twist and translation
    if "t" in DVs or "v" in DVs or "h" in DVs:
        # Define a reference axis
        nTwist = DVGeoWing.addRefAxis("wing_axis", xFraction=0.25, alignIndex="j", rotType=4, volumes=[0])

    # Add wing twist design variables
    if "t" in DVs:

        def twist(val, geo):
            # Set all the twist values
            for i in range(nTwist):
                geo.rot_y["wing_axis"].coef[i] = val[i]

        DVGeoWing.addGlobalDV("twist", func=twist, value=np.zeros(nTwist), lower=-4, upper=4, scale=0.1)

    # Add wing shape design variables
    if "s" in DVs:
        DVGeoWing.addLocalDV("shape", lower=-0.01, upper=0.01, axis="z", scale=100.0)

    # Add wing horizontal displacement variable
    if "h" in DVs:

        def wing_x(val, geo):
            # Extract control points of the reference axis
            C = geo.extractCoef("wing_axis")

            # Translate each control point in x
            for i in range(len(C)):
                C[i, 0] = C[i, 0] + val[0]

            # Restore control points to the reference axis
            geo.restoreCoef(C, "wing_axis")

        DVGeoWing.addGlobalDV("wing_x", func=wing_x, value=0.0, lower=-0.1, upper=0.1, scale=1.0)

    # Add wing vertical displacement variable
    if "v" in DVs:

        def wing_z(val, geo):
            # Extract control points of the reference axis
            C = geo.extractCoef("wing_axis")

            # Translate each control point in z
            for i in range(len(C)):
                C[i, 2] = C[i, 2] + val[0]

            # Restore control points to the reference axis
            geo.restoreCoef(C, "wing_axis")

        DVGeoWing.addGlobalDV("wing_z", func=wing_z, value=0.0, lower=-0.1, upper=0.1, scale=0.1)

Fuselage design variables

We also define fuselage design variables using a few fuselage control points near the wing-fuselage intersection. We want these points to move normal to the fuselage surface, as shown below.

../_images/intersection_fuse_dv.jpeg

A few fuselage control points move normal to the fuselage surface

We use PointSelect to select which FFD control points should be able to move. We then define local section design variables using these points.

    # Add fuselage design variables
    if "f" in DVs:
        # Create point select to get specific points for the normal variables
        ijkBounds = {0: [[2, -3], [0, 2], [3, -4]]}
        PS = geo_utils.PointSelect("ijkBounds", ijkBounds=ijkBounds)

        # Add normal pertubations to the fairing section
        DVGeoFuse.addLocalSectionDV("normals", "k", pointSelect=PS, lower=-0.04, upper=0.00, scale=200.0)

Defining local section design variables involves selecting the local 0, 1, 2 axis directions. For the FFD volume of interest, setting secIndex="k" defines the 2-direction in the circumferential direction. By default, orient0 is None, which results in the 0-direction pointing along the length of the fuselage. This leaves the 1-direction as the radial direction. By default, axis is 1, so the shape deformations are defined in the radial direction as expected. See addLocalSectionDV for details on how each direction is defined in general.

If we wanted to give the optimizer more freedom in changing the fuselage shape, we could add local shape variables in the y and z directions instead of specifying the normal direction. This can be done by calling addLocalDV once with axis="y" and once with axis="z".

Geometric constraints

Geometric constraints are defined in SETUP/setup_dvcon.py. When using wing shape variables, we define geometric constraints similar to a wing-only optimization. However, there are some differences because we are dealing with multiple FFDs.

For leading edge and trailing edge constraints, we must pass the comp argument. This defines which FFD the constraint is being applied to.

        # Add LE/TE constraints
        DVCon.addLeTeConstraints(0, "iLow", comp="wing")
        DVCon.addLeTeConstraints(0, "iHigh", comp="wing")

For thickness constraints, we can pass the compNames argument. This is not strictly required, but it reduces the computations required by DVGeometryMulti when adding and updating the point set.

        DVCon.addThicknessConstraints2D(
            name="thickness",
            leList=leList,
            teList=teList,
            nSpan=10,
            nChord=10,
            lower=1.0,
            upper=3.0,
            compNames=["wing"],
        )