dragonfly_sim.utils.ffd_dv_utils ================================ .. py:module:: dragonfly_sim.utils.ffd_dv_utils .. autoapi-nested-parse:: 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. .. !! processed by numpydoc !! Classes ------- .. autoapisummary:: dragonfly_sim.utils.ffd_dv_utils.CPMotionDV dragonfly_sim.utils.ffd_dv_utils.ShapeDVSet Functions --------- .. autoapisummary:: dragonfly_sim.utils.ffd_dv_utils._resolve_direction_bounds dragonfly_sim.utils.ffd_dv_utils.build_cp_motion_dv dragonfly_sim.utils.ffd_dv_utils.build_sectional_dv dragonfly_sim.utils.ffd_dv_utils.cp_dv_directions dragonfly_sim.utils.ffd_dv_utils.print_cp_motion_bounds dragonfly_sim.utils.ffd_dv_utils.spanwise_linear_bounds Module Contents --------------- .. py:class:: CPMotionDV Bases: :py:obj:`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 .. !! processed by numpydoc !! .. py:attribute:: coord_idxs :type: numpy.ndarray .. py:attribute:: lower :type: numpy.ndarray .. py:attribute:: scaler :type: Optional[numpy.ndarray] .. py:attribute:: shape :type: tuple .. py:attribute:: upper :type: numpy.ndarray .. py:attribute:: value :type: numpy.ndarray .. py:class:: 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``. .. !! processed by numpydoc !! .. py:method:: __getitem__(name) .. py:method:: add(name, dv) Create the csdl design variable for `dv` (a CPMotionDV) and return it. .. !! processed by numpydoc !! .. py:method:: dv(name) .. py:method:: 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. .. !! processed by numpydoc !! .. py:attribute:: dvs :value: [] .. py:attribute:: names :value: [] .. py:property:: shape .. py:property:: size .. py:attribute:: variables :value: [] .. py:function:: _resolve_direction_bounds(bound_spec, coords, direction) One direction's bounds as a pair of (num_control_points,) arrays. .. !! processed by numpydoc !! .. py:function:: 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]]. .. !! processed by numpydoc !! .. py:function:: build_sectional_dv(num_values, bounds, scaler_mode='bounds') Arrays for one sectional shape variable (twist, camber, ...). num_values : entries of the variable -- one per section, or fewer for a B-spline profile over the sections (see SectionalShape) bounds : a 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. .. !! processed by numpydoc !! .. py:function:: 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. .. !! processed by numpydoc !! .. py:function:: 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. .. !! processed by numpydoc !! .. py:function:: 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. .. !! processed by numpydoc !!