dragonfly_sim.utils.ffd_dv_utils

Construction of the FFD control-point-motion design variable and its bounds.

The design variable cp_motions has shape ffd_shape + (n_opt,), where the trailing axis is a compressed index over the optimized spatial directions – cp_motions[..., m] moves spatial component coord_idxs[m] of every control point, and directions absent from coord_idxs stay at their baseline value. See shape_parameterization.WallFFD.apply_cp_motions for the application of that mapping.

Everything here is expressed through a single dv_spec dict, keyed by spatial direction index, so the choice of optimized directions and the bounds on each of them cannot drift apart:

dv_spec = {

0: 0.02, # x: symmetric +/- 0.02 everywhere 2: (-0.05, 0.10), # z: asymmetric, uniform over control points

}

A value may also be a callable f(coords) -> (lower, upper) for bounds that vary over the block; see spanwise_linear_bounds.

Sectional shape variables (shape_parameterization.SectionalShape) get their arrays from build_sectional_dv, and ShapeDVSet collects any mix of these into the ordered set of shape design variables a driver declares.

Numpy-only apart from ShapeDVSet’s variable creation, which imports csdl lazily – so the bounds logic can be exercised without an MPI environment. Callers pass their own printer (typically PETSc.Sys.Print) to print_cp_motion_bounds.

Classes

CPMotionDV

Everything needed to declare cp_motions as a design variable.

ShapeDVSet

The ordered set of shape design variables of a driver.

Functions

_resolve_direction_bounds(bound_spec, coords, direction)

One direction's bounds as a pair of (num_control_points,) arrays.

build_cp_motion_dv(baseline_controlpoints, dv_spec[, ...])

Build the cp_motions design variable arrays from a dv_spec.

build_sectional_dv(num_values, bounds[, scaler_mode])

Arrays for one sectional shape variable (twist, camber, ...).

cp_dv_directions(dv_spec)

The optimized spatial components named by dv_spec, sorted ascending.

print_cp_motion_bounds(cp_dv[, printer, axis_names])

Summarise a CPMotionDV, one line per optimized direction.

spanwise_linear_bounds(base_bound, scale_at_root, ...)

A bound callable whose magnitude varies linearly with the spanwise coordinate.

Module Contents

class dragonfly_sim.utils.ffd_dv_utils.CPMotionDV

Bases: NamedTuple

Everything needed to declare cp_motions as a design variable.

coord_idxs : (n_opt,) int array of optimized spatial components, ascending shape : ffd_shape + (n_opt,), the shape of the design variable value : initial value (zeros – the motions are deltas from baseline) lower/upper: bounds, in physical units, same shape as value scaler : per-entry scaler for set_as_design_variable, or None

coord_idxs: numpy.ndarray
lower: numpy.ndarray
scaler: numpy.ndarray | None
shape: tuple
upper: numpy.ndarray
value: numpy.ndarray
class dragonfly_sim.utils.ffd_dv_utils.ShapeDVSet

The ordered set of shape design variables of a driver.

flat_variable concatenates them, in declaration order and each variable flattened in C order, into the one vector the model takes as its cp_motion_inputs.

__getitem__(name)
add(name, dv)

Create the csdl design variable for dv (a CPMotionDV) and return it.

dv(name)
flat_variable()

All shape variables as one csdl vector, in declaration order.

This is what the model takes as its cp_motion_inputs. A single variable is returned as is.

dvs = []
names = []
property shape
property size
variables = []
dragonfly_sim.utils.ffd_dv_utils._resolve_direction_bounds(bound_spec, coords, direction)

One direction’s bounds as a pair of (num_control_points,) arrays.

dragonfly_sim.utils.ffd_dv_utils.build_cp_motion_dv(baseline_controlpoints, dv_spec, scaler_mode='bounds')

Build the cp_motions design variable arrays from a dv_spec.

baseline_controlpoints(*ffd_shape, ndim) control point coordinates, as

held by shape_parameterization.WallFFD.baseline_coefficients

dv_spec : {direction: bound_spec}, see the module docstring scaler_mode : ‘bounds’ derives a scaler of 1/half-range so every direction’s

box becomes +/-1 in the optimizer’s space; None emits no scaler.

Returns a CPMotionDV. CSDL flattens design variables and their bounds in C order (csdl_alpha/backends/simulator.py, build_opt_metadata), and this function reshapes in C order too, so no manual index bookkeeping is needed: entry [i, j, k, m] bounds the motion of baseline_controlpoints[i, j, k, coord_idxs[m]].

dragonfly_sim.utils.ffd_dv_utils.build_sectional_dv(num_values, bounds, scaler_mode='bounds')

Arrays for one sectional shape variable (twist, camber, …).

num_valuesentries of the variable – one per section, or fewer for a

B-spline profile over the sections (see SectionalShape)

boundsa scalar symmetric half-range, a (lower, upper) pair, or a pair of

(num_values,) arrays. As for cp_motions the variable starts at zero (a delta from the baseline), so zero must lie inside the box; pin an entry with (0., 0.).

scaler_mode : as in build_cp_motion_dv

Returns a CPMotionDV with coord_idxs=None.

dragonfly_sim.utils.ffd_dv_utils.cp_dv_directions(dv_spec)

The optimized spatial components named by dv_spec, sorted ascending.

Call this before constructing DG_windtunnel_model, which needs cp_coord_opt_idxs up front; build_cp_motion_dv (which needs the baseline control points, and so can only run after set_up_sim) derives the same array again and returns it for cross-checking.

Sorting is load-bearing: it makes the design variable’s column order independent of the order the dict literal happens to be written in, and shape_parameterization.validate_cp_coord_opt_idxs enforces the same strictly-increasing invariant.

dragonfly_sim.utils.ffd_dv_utils.print_cp_motion_bounds(cp_dv, printer=print, axis_names=('x', 'y', 'z'))

Summarise a CPMotionDV, one line per optimized direction.

printer is called once per line; pass PETSc.Sys.Print from an MPI driver to keep the output on rank 0.

dragonfly_sim.utils.ffd_dv_utils.spanwise_linear_bounds(base_bound, scale_at_root, scale_at_ref, y_ref, y_axis=1)

A bound callable whose magnitude varies linearly with the spanwise coordinate.

The bound is base_bound*scale_at_root at y = 0 and base_bound*scale_at_ref at y = y_ref, linearly interpolated in between and clipped to that range outside [0, y_ref] so the magnitude never changes sign. Bounds are symmetric: lower = -bound, upper = +bound.

Motivating case: a wing FFD block that tapers from root chord to tip chord, so a single scalar bound is a much larger fraction of the local chord at the tip than at the root. Shrinking the bound along the span keeps the allowed deformation a roughly constant fraction of the local chord.