dragonfly_sim.core.shape_parameterization

Wall-shape parameterization built on lsdo_geo.

Maps shape design variables to the motion of the mesh wall nodes, and measures the deformed wall for geometric constraints. Three pieces:

  • WallFFD – an lsdo_geo.FFDBlock around the wall nodes. Its output, wall_displacement(C), is the replicated (M_global, dim) array that IDWarp_jax.evaluate consumes.

  • Shape layers that produce the block’s coefficients C: SectionalShape (lsdo_geo SectionalParameterization: camber/thickness per chordwise station in 2D; twist/chord/sweep/dihedral/span per spanwise section in 3D) and WallFFD.apply_cp_motions (raw control-point motions). Compose them as C = apply_cp_motions(SectionalShape.apply(C0), cp_motions) – see SectionalShape for why the sectional layer must act on the constant baseline.

  • WallGeometry – geometric constraint quantities of the deformed wall: enclosed area (2D) / volume (3D), thickness at stations, and 3D planform (section chords, span, planform area).

Everything here is csdl_alpha graph code on replicated arrays, so derivatives come from csdl’s autodiff and every rank computes identical values.

Classes

SectionalShape

Sectional shape variables on a WallFFD, via lsdo_geo's SectionalParameterization.

WallFFD

lsdo_geo FFD block embedding the mesh wall nodes.

WallGeometry

Geometric constraint quantities of the FFD-deformed wall.

Functions

_selection_matrix(ids, n_cols)

ffd_corners_from_bounding_box(coords[, margin])

Axis-aligned FFD block corners around coords, widened by margin*extent.

ffd_corners_from_corner_list(ffd_block_corner_list[, ...])

FFD block corners lofted between two rectangular end surfaces.

validate_cp_coord_opt_idxs(cp_coord_opt_idxs, dim)

Check and return the optimized control-point directions as an int array.

Module Contents

class dragonfly_sim.core.shape_parameterization.SectionalShape(ffd, principal_dim=0)

Sectional shape variables on a WallFFD, via lsdo_geo’s SectionalParameterization.

The block is seen as a stack of sections normal to principal_dim: in 2D use principal_dim = 0 (chordwise stations, each a vertical column of control points); for a wing, the spanwise parametric direction. Each variable holds either one value per section, or – when it has fewer entries – the coefficients of a B-spline profile over the sections.

The sectional layer must act on the constant baseline coefficients. lsdo_geo takes stretch and translation axes, and stretch origins, from the points’ values when the graph is built, so feeding it DV-dependent points would bake the initial DV values into the map. Add raw control-point motions afterwards (WallFFD.apply_cp_motions).

Named presets (add), with lsdo_geo’s conventions: a stretch is the additive change in section extent along the axis (linear across the section, zero at the pivot); a rotation is in radians about the axis through the pivot; a translation is a length.

kind

operation

dims

camber

translation along the vertical axis

2D

thickness

stretch along the vertical param. axis

2D, 3D

twist

rotation about the principal axis

3D

chord

stretch along parametric axis 0

3D

sweep

translation along x

3D

dihedral

translation along z

3D

span

translation along y

3D

_check_size(values)
_per_section(values, profile_degree=2)
_section_pivot(pivot)
add(kind, values, pivot=None, profile_degree=2)

Add a named sectional variable; see the class docstring.

add_rotation(values, parametric_axis=None, pivot=None, profile_degree=2)
add_stretch(values, parametric_axis, pivot=None, profile_degree=2)
add_translation(values, direction, profile_degree=2)
apply(coefficients)

Sectionally deformed coefficients. Pass ffd.baseline_variable().

_specs = []
dim
ffd
num_sections
principal_dim = 0
class dragonfly_sim.core.shape_parameterization.WallFFD(comm, global_wall_coords, local_rows, shape, degree=2, ffd_block_corner_list=None, margin=0.0001, projection_newton_tol=1e-12, name='wall_ffd')

lsdo_geo FFD block embedding the mesh wall nodes.

Replicated across ranks: every rank evaluates the FFD at all M_global wall nodes and returns the full global array, rather than evaluating only the nodes it owns and having the pieces assembled afterwards.

That is deliberate. A rank owning no wall nodes would otherwise return a (0, dim) array, and the reverse chain back to the design variables would reduce an empty array – which XLA constant-folds at compile time. The downstream gradient reduction would then have no data dependence on anything on that rank, and since jax.pure_callback is declared effect-free, its collective could be scheduled arbitrarily early relative to the other ranks’. That is an ordering deadlock the ranks cannot recover from. Evaluating the full wall everywhere keeps every rank’s chain non-degenerate and lets the gradient reduction happen inside IDWarp_jax.compute_jacvec_product, on an array every rank genuinely computes.

Only the projection is rank-local: each rank projects the wall nodes it owns (local_rows) and the resulting parametric coordinates are gathered in rank order.

Parameters:
commmpi4py communicator
global_wall_coords(M_global, dim) array

All wall node coordinates, in the rank-block order of Mesh.boundary_node_set((wall_tag,)).coords.

local_rowsint array

The contiguous rows of global_wall_coords this rank owns (that node set’s global_idx).

shapesequence of int

Control points per parametric direction.

degreeint or sequence of int
ffd_block_corner_listoptional

Two-surface block definition, see ffd_corners_from_corner_list. None wraps the wall’s bounding box.

marginfloat

Relative widening of the block beyond the wall / the given surfaces.

projection_newton_tolfloat

Newton tolerance of the wall node projection into the block.

_displacement_at(parametric_coordinates, coefficients)
_project(points)
add_probe_points(points)

Embed extra points (replicated input); returns their probe index.

Projected on rank 0 and broadcast, so every rank holds identical parametric coordinates.

apply_cp_motions(coefficients, cp_motions, cp_coord_opt_idxs)

Add raw control-point motions to coefficients.

cp_motions has shape ffd_shape + (len(cp_coord_opt_idxs),); column i moves spatial component cp_coord_opt_idxs[i] and the other directions are left unchanged. The index tuples are built rather than written as literals so this works for any dimension.

baseline_variable()

The baseline coefficients as a constant csdl Variable.

probe_positions(coefficients, probe)

Deformed positions of probe set probe (index from add_probe_points).

wall_displacement(coefficients)

Replicated (M_global, dim) wall node displacement for coefficients.

_probe_points = []
baseline_coefficients
baseline_wall_coords
block
comm
corners
degree
projection_newton_tol = 1e-12
shape
wall_parametric_coordinates
class dragonfly_sim.core.shape_parameterization.WallGeometry(ffd, wall_facets)

Geometric constraint quantities of the FFD-deformed wall.

Parameters:
ffdWallFFD
wall_facets(n_facets, k) int array

Replicated wall facets as rows of wall node ids in [0, M_global) (Mesh.wall_facets(wall_tag)): segments in 2D, triangles or quads (cyclic vertex order) in 3D. Their winding only needs to be consistent; the sign of the enclosed measure is fixed from the baseline.

In 3D the wall may be open on a symmetry plane through the origin (y = 0):
that plane contributes nothing to the divergence-theorem volume, as long as
the root section keeps y = 0.
_chord_range(y0=None)
_gather(X, j)
_raw_measure_numpy(X)
_section_leading_trailing_edges(y0)

Baseline (LE, TE) points of the 3D wall’s section at span y0: the min-x and max-x points where the plane y = y0 cuts the wall.

_vertical_line_hits(x_in_plane)

Vertical-coordinate values where the vertical line through x_in_plane (x in 2D, (x, y) in 3D) crosses the baseline wall.

add_planform_stations(span_stations)

Embed the LE and TE points of the wall sections at the given span stations (increasing y, root first); returns a handle.

handle['baseline'] holds the undeformed chord, span and area as numbers, for constraint bounds (the csdl values are not available while the graph is recorded).

add_thickness_stations(chord_fractions, span_stations=None)

Embed upper/lower wall points at chordwise stations; returns a handle.

2D: one station per chord fraction. 3D: the tensor product of chord fractions (of the local section chord) and span stations (y values).

enclosed_measure(wall_displacement)

Enclosed area (2D) or volume (3D) of the deformed wall, positive.

planform(coefficients, handle)

Deformed section chords, span and trapezoidal planform area.

Chords are LE-TE distances projected onto the xy plane; span and area use the y coordinates of the leading edges.

thickness(coefficients, handle)

Deformed thickness at the stations of handle.

thickness_ratio(coefficients, handle)

Deformed over baseline thickness at the stations of handle.

_selectors
_sign
baseline_measure
dim
ffd
dragonfly_sim.core.shape_parameterization._selection_matrix(ids, n_cols)
dragonfly_sim.core.shape_parameterization.ffd_corners_from_bounding_box(coords, margin=0.0001)

Axis-aligned FFD block corners around coords, widened by margin*extent.

Returns the (2,)*dim + (dim,) corner array that lfs.create_b_spline_from_corners takes: corners[i0, i1, ..., d] is the min (i_d = 0) or max (i_d = 1) of coordinate d.

dragonfly_sim.core.shape_parameterization.ffd_corners_from_corner_list(ffd_block_corner_list, margin=0.0001)

FFD block corners lofted between two rectangular end surfaces.

ffd_block_corner_list holds exactly two surfaces, each given by two opposite corners, e.g. the root and tip rectangles of a wing block. The “connecting” direction is the axis along which the surface midpoints differ most (spanwise for a wing); each surface spans the remaining axes. Each surface is widened by margin*extent along the axes it spans. The resulting block is tapered/swept, and lfs.create_b_spline_from_corners fills it with the same trilinear lattice the pre-lsdo_geo construction produced.

dragonfly_sim.core.shape_parameterization.validate_cp_coord_opt_idxs(cp_coord_opt_idxs, dim)

Check and return the optimized control-point directions as an int array.

Strictly increasing is required, not merely unique: the cp_motions design variable’s trailing axis is a compressed index into this array (see WallFFD.apply_cp_motions), so a fixed order is what lets the bounds built by ffd_dv_utils.build_cp_motion_dv line up with the DV columns without either side having to know how the other was written.