dragonfly_sim.core.shape_parameterization ========================================= .. py:module:: dragonfly_sim.core.shape_parameterization .. autoapi-nested-parse:: 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. .. !! processed by numpydoc !! Classes ------- .. autoapisummary:: dragonfly_sim.core.shape_parameterization.SectionalShape dragonfly_sim.core.shape_parameterization.WallFFD dragonfly_sim.core.shape_parameterization.WallGeometry Functions --------- .. autoapisummary:: dragonfly_sim.core.shape_parameterization._selection_matrix dragonfly_sim.core.shape_parameterization.ffd_corners_from_bounding_box dragonfly_sim.core.shape_parameterization.ffd_corners_from_corner_list dragonfly_sim.core.shape_parameterization.validate_cp_coord_opt_idxs Module Contents --------------- .. py:class:: 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 ========== ======================================== ======= .. !! processed by numpydoc !! .. py:method:: _check_size(values) .. py:method:: _per_section(values, profile_degree=2) .. py:method:: _section_pivot(pivot) .. py:method:: add(kind, values, pivot=None, profile_degree=2) Add a named sectional variable; see the class docstring. .. !! processed by numpydoc !! .. py:method:: add_rotation(values, parametric_axis=None, pivot=None, profile_degree=2) .. py:method:: add_stretch(values, parametric_axis, pivot=None, profile_degree=2) .. py:method:: add_translation(values, direction, profile_degree=2) .. py:method:: apply(coefficients) Sectionally deformed coefficients. Pass ``ffd.baseline_variable()``. .. !! processed by numpydoc !! .. py:attribute:: _specs :value: [] .. py:attribute:: dim .. py:attribute:: ffd .. py:attribute:: num_sections .. py:attribute:: principal_dim :value: 0 .. py:class:: 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: **comm** : mpi4py 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_rows** : int array The contiguous rows of ``global_wall_coords`` this rank owns (that node set's ``global_idx``). **shape** : sequence of int Control points per parametric direction. **degree** : int or sequence of int .. **ffd_block_corner_list** : optional Two-surface block definition, see ``ffd_corners_from_corner_list``. None wraps the wall's bounding box. **margin** : float Relative widening of the block beyond the wall / the given surfaces. **projection_newton_tol** : float Newton tolerance of the wall node projection into the block. .. !! processed by numpydoc !! .. py:method:: _displacement_at(parametric_coordinates, coefficients) .. py:method:: _project(points) .. py:method:: 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. .. !! processed by numpydoc !! .. py:method:: 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. .. !! processed by numpydoc !! .. py:method:: baseline_variable() The baseline coefficients as a constant csdl Variable. .. !! processed by numpydoc !! .. py:method:: probe_positions(coefficients, probe) Deformed positions of probe set `probe` (index from add_probe_points). .. !! processed by numpydoc !! .. py:method:: wall_displacement(coefficients) Replicated ``(M_global, dim)`` wall node displacement for `coefficients`. .. !! processed by numpydoc !! .. py:attribute:: _probe_points :value: [] .. py:attribute:: baseline_coefficients .. py:attribute:: baseline_wall_coords .. py:attribute:: block .. py:attribute:: comm .. py:attribute:: corners .. py:attribute:: degree .. py:attribute:: projection_newton_tol :value: 1e-12 .. py:attribute:: shape .. py:attribute:: wall_parametric_coordinates .. py:class:: WallGeometry(ffd, wall_facets) Geometric constraint quantities of the FFD-deformed wall. :Parameters: **ffd** : WallFFD .. **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.** .. .. !! processed by numpydoc !! .. py:method:: _chord_range(y0=None) .. py:method:: _gather(X, j) .. py:method:: _raw_measure_numpy(X) .. py:method:: _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. .. !! processed by numpydoc !! .. py:method:: _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. .. !! processed by numpydoc !! .. py:method:: 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). .. !! processed by numpydoc !! .. py:method:: 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). .. !! processed by numpydoc !! .. py:method:: enclosed_measure(wall_displacement) Enclosed area (2D) or volume (3D) of the deformed wall, positive. .. !! processed by numpydoc !! .. py:method:: 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. .. !! processed by numpydoc !! .. py:method:: thickness(coefficients, handle) Deformed thickness at the stations of `handle`. .. !! processed by numpydoc !! .. py:method:: thickness_ratio(coefficients, handle) Deformed over baseline thickness at the stations of `handle`. .. !! processed by numpydoc !! .. py:attribute:: _selectors .. py:attribute:: _sign .. py:attribute:: baseline_measure .. py:attribute:: dim .. py:attribute:: ffd .. py:function:: _selection_matrix(ids, n_cols) .. py:function:: 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. .. !! processed by numpydoc !! .. py:function:: 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. .. !! processed by numpydoc !! .. py:function:: 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. .. !! processed by numpydoc !!