Shape parameterization

The wall shape is controlled by a B-spline FFD block around the wall nodes (WallFFD, created by DG_windtunnel_model.set_up_sim() as model.ffd). Design variables change the block’s control points; the wall moves with them, and the volume mesh follows through IDWarp.

FFD block

ffd_shape sets the number of control points per parametric direction (2 entries in 2D, 3 in 3D) and ffd_degree the B-spline degree. By default the block is the axis-aligned bounding box of the wall. For a tapered or swept wing, pass ffd_block_corner_list to DG_windtunnel_model: two rectangular end surfaces (root and tip), each given by two opposite corners, e.g. [[(-0.1, 0., 0.32), (5.1, 0., -0.3)], [(7.4, 14.1, 0.32), (9.1, 14.1, -0.3)]].

Control-point motions

cp_motions moves control points individually. Its directions and bounds come from one dictionary keyed by spatial direction (0 = x, 1 = y, 2 = z):

dv_spec = {1: 0.01}                     # 2D: vertical motions within +/-0.01
dv_spec = {0: 0.02, 2: (-0.05, 0.10)}   # 3D: x symmetric, z asymmetric
dv_spec = {2: spanwise_linear_bounds(base_bound=0.1, scale_at_root=1.0, scale_at_ref=0.5, y_ref=14.1)}

A bound is a symmetric half-range, a (lower, upper) pair, or a callable of the control-point coordinates such as spanwise_linear_bounds, which varies the bound linearly along the span.

  • cp_dv_directions(dv_spec) gives the cp_coord_opt_idxs that DG_windtunnel_model needs.

  • build_cp_motion_dv(model.ffd.baseline_coefficients, dv_spec) builds the variable’s value, bounds and scaling (each direction’s box maps to \(\pm 1\)).

  • model.ffd.apply_cp_motions(coefficients, cp_motions, cp_coord_opt_idxs) adds the motions to the control points.

Sectional variables

SectionalShape(model.ffd, principal_dim) views the block as a stack of sections: principal_dim=0 in 2D (chordwise stations), 1 for a wing (spanwise stations). Variables are added with add(kind, values):

Kind

Operation

Dimensions

camber

translation along the vertical axis

2D

thickness

stretch along the vertical axis

2D, 3D

twist

rotation about the spanwise axis (radians)

3D

chord

stretch along the chordwise axis

3D

sweep

translation along x

3D

dihedral

translation along z

3D

span

translation along y

3D

A variable has one value per section, or fewer values, which are then B-spline coefficients of a profile over the sections. Create its bounds with build_sectional_dv(num_values, bounds).

Apply the sectional layer to the constant baseline first, then the control-point motions:

sectional = SectionalShape(model.ffd, principal_dim=1)
twist = shape_dvs.add('twist', build_sectional_dv(5, np.radians(2.)))
sectional.add('twist', twist)
coefficients = sectional.apply(model.ffd.baseline_variable())
coefficients = model.ffd.apply_cp_motions(coefficients, cp_motions, cp_coord_opt_idxs)

Design-variable set

ShapeDVSet collects all shape variables in declaration order; shape_dvs.flat_variable() is the vector passed to the mesh warper, flow model and postprocessor.

Geometric constraints

model.wall_geometry() returns a WallGeometry for the deformed wall:

Method

Quantity

enclosed_measure(wall_displacement)

enclosed area (2D) or volume (3D)

add_thickness_stations(chord_fractions, span_stations=None) then thickness_ratio(coefficients, handle)

deformed over baseline thickness at the stations

add_planform_stations(span_stations) then planform(coefficients, handle)

section chords, span and planform area (3D)

geometry = model.wall_geometry()
area = geometry.enclosed_measure(model.ffd.wall_displacement(coefficients))
area.set_as_constraint(lower=geometry.baseline_measure)
stations = geometry.add_thickness_stations(np.linspace(0.1, 0.9, 9))
geometry.thickness_ratio(coefficients, stations).set_as_constraint(lower=0.9)