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.
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.
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.
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.
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"],
)