dragonfly_sim.core.shape_design =============================== .. py:module:: dragonfly_sim.core.shape_design .. autoapi-nested-parse:: Shape design variables as modular layers on an lsdo_geo FFD block. ``DG_windtunnel_model`` only sees an ``FFDShapeParameterization``: it hands it the wall nodes in ``set_up_sim`` (``setup``) and takes the resulting wall displacement into the mesh warper (``deform_mesh``). Which design variables exist, their bounds, and how they move the control points is decided entirely by the layers given to the parameterization: * ``ControlPointMotions`` -- raw control-point motions (``cp_motions``). * ``SectionalVariables`` -- the ``SectionalShape`` presets (2D camber and thickness per chordwise station; 3D twist, chord, ... per spanwise section). * ``WingShape`` -- a trapezoidal wing: quarter-chord (or any chord-line) sweep, aspect ratio, root chord, span and taper ratio, plus chordwise thickness and camber modes per spanwise station. A driver then reads:: shape = FFDShapeParameterization(ffd_shape, degree, corner_list, layers=[WingShape(planform={...}, sections={...})]) model = DG_windtunnel_model(mesh, boundary_dict, shape, ...) model.set_up_sim() shape_parameters = shape.declare_design_variables() ... u_vec = model.evaluate(model.deform_mesh(), shape_parameters, alpha) and constrains geometry through ``shape.outputs()`` (layer quantities such as the wing's planform area) and ``shape.geometry`` (``WallGeometry``: enclosed measure, thickness at stations, planform stations). The coefficient pipeline is ``C0 -> one lsdo_geo SectionalParameterization -> additive layers``. lsdo_geo freezes axes and stretch origins from the points' values when the graph is built, so every layer's sectional operations are collected into a single evaluation on the constant baseline (and must share one principal direction); additive motions (cp motions, sectional modes) are linear and go on top. A new layer subclasses ``ShapeLayer`` and overrides what it needs. .. !! processed by numpydoc !! Classes ------- .. autoapisummary:: dragonfly_sim.core.shape_design.ControlPointMotions dragonfly_sim.core.shape_design.FFDShapeParameterization dragonfly_sim.core.shape_design.SectionalVariables dragonfly_sim.core.shape_design.ShapeLayer dragonfly_sim.core.shape_design.WingShape Functions --------- .. autoapisummary:: dragonfly_sim.core.shape_design._bernstein dragonfly_sim.core.shape_design._bspline_basis dragonfly_sim.core.shape_design._invert_monotone_bspline dragonfly_sim.core.shape_design._section_mode_profiles Module Contents --------------- .. py:class:: ControlPointMotions(dv_spec, scaler_mode='bounds', name='cp_motions') Bases: :py:obj:`ShapeLayer` Raw control-point motions: one design variable ``cp_motions`` of shape ``ffd_shape + (n_opt,)``, with bounds from ``dv_spec`` (see ffd_dv_utils). .. !! processed by numpydoc !! .. py:method:: additive(coefficients) .. py:method:: declare(dvs) Create this layer's design variables in ``dvs`` (a ShapeDVSet). .. !! processed by numpydoc !! .. py:method:: describe() Lines for ``FFDShapeParameterization.print_design_variables``. .. !! processed by numpydoc !! .. py:attribute:: coord_idxs .. py:attribute:: dv_spec .. py:attribute:: name :value: 'cp_motions' .. py:attribute:: scaler_mode :value: 'bounds' .. py:class:: FFDShapeParameterization(shape, degree=2, block_corner_list=None, layers=(), margin=0.0001, projection_newton_tol=1e-12, inner_bdry_rel_tol=1e-06, inner_bdry_overset_margin=0.0001) An FFD block around the wall plus the design-variable layers that move it. :Parameters: **shape** : sequence of int Control points per parametric direction. **degree** : int or sequence of int .. **block_corner_list** : optional Two-surface block definition (``shape_parameterization. ffd_corners_from_corner_list``); None wraps the wall's bounding box. Also locates the wall when the model gets no explicit ``mesh_inner_bdry_function`` (``inner_boundary_function``). **layers** : sequence of ShapeLayer Applied in order; their design variables are declared in this order. **margin, projection_newton_tol** : see ``WallFFD``. .. **inner_bdry_rel_tol, inner_bdry_overset_margin** : containment test of ``inner_boundary_function``. .. !! processed by numpydoc !! .. py:method:: __getitem__(name) A declared design variable by name. .. !! processed by numpydoc !! .. py:method:: _require_setup() .. py:method:: coefficients() Deformed FFD coefficients (built once, then cached). .. !! processed by numpydoc !! .. py:method:: declare_design_variables() Create every layer's csdl design variables (with bounds), in layer order. Returns ``parameter_vector()``. Call inside the recorder, after ``set_up_sim``. .. !! processed by numpydoc !! .. py:method:: inner_boundary_function() Wall facet marker from the block corners, or None without them. .. !! processed by numpydoc !! .. py:method:: outputs() Constraint quantities of the layers: ``{layer.name: {...}}`` for the layers that have any (e.g. ``outputs()['wing']['area']``). .. !! processed by numpydoc !! .. py:method:: parameter_vector() All shape design variables as one flat vector (declaration order). .. !! processed by numpydoc !! .. py:method:: print_design_variables(printer=print) .. py:method:: setup(comm, wall_coords, wall_rows, wall_facets=None) Build the FFD block around the wall and set up the layers. Collective. wall_coords : (M_global, dim) replicated wall node coordinates, in the rank-block order of the mesh's wall node set; wall_rows : the rows this rank owns; wall_facets : replicated wall facets (rows of wall node ids), needed for ``geometry`` and by layers that measure the baseline wall. .. !! processed by numpydoc !! .. py:method:: wall_displacement() Replicated ``(M_global, dim)`` wall node displacement (cached). .. !! processed by numpydoc !! .. py:attribute:: _coefficients :value: None .. py:attribute:: _geometry :value: None .. py:attribute:: _wall_displacement :value: None .. py:attribute:: _wall_facets :value: None .. py:attribute:: block_corner_list :value: None .. py:attribute:: degree :value: 2 .. py:property:: dim .. py:attribute:: dvs :value: None .. py:attribute:: ffd :value: None .. py:property:: geometry ``WallGeometry`` of the baseline wall, built on first use. .. !! processed by numpydoc !! .. py:attribute:: inner_bdry_overset_margin :value: 0.0001 .. py:attribute:: inner_bdry_rel_tol :value: 1e-06 .. py:attribute:: layers :value: [] .. py:attribute:: margin :value: 0.0001 .. py:attribute:: projection_newton_tol :value: 1e-12 .. py:attribute:: shape .. py:class:: SectionalVariables(principal_dim, variables, scaler_mode='bounds', name='sectional') Bases: :py:obj:`ShapeLayer` ``SectionalShape`` presets as design variables. variables : {kind: spec}, kind one of ``SectionalShape.add``'s presets; spec is the bounds (anything ``build_sectional_dv`` takes) or a dict with keys ``bounds`` and optionally ``num_values`` (fewer than the number of sections: a B-spline profile), ``pivot`` and ``profile_degree``. Each variable is a delta from the baseline. .. !! processed by numpydoc !! .. py:method:: declare(dvs) Create this layer's design variables in ``dvs`` (a ShapeDVSet). .. !! processed by numpydoc !! .. py:method:: sectional(sectional) Register sectional operations on ``sectional`` (a SectionalShape). .. !! processed by numpydoc !! .. py:attribute:: name :value: 'sectional' .. py:attribute:: principal_dim .. py:attribute:: scaler_mode :value: 'bounds' .. py:attribute:: specs .. py:class:: ShapeLayer Base of the design-variable layers of an FFDShapeParameterization. A layer with sectional (lsdo_geo) operations sets ``principal_dim`` and registers them in ``sectional``; that call happens inside an inline recording, so expressions of the design variables built there have values, which lsdo_geo needs. ``additive`` adds motions that are linear in the variables on top of the sectional result. .. !! processed by numpydoc !! .. py:method:: additive(coefficients) .. py:method:: declare(dvs) Create this layer's design variables in ``dvs`` (a ShapeDVSet). .. !! processed by numpydoc !! .. py:method:: describe() Lines for ``FFDShapeParameterization.print_design_variables``. .. !! processed by numpydoc !! .. py:method:: outputs() .. py:method:: sectional(sectional) Register sectional operations on ``sectional`` (a SectionalShape). .. !! processed by numpydoc !! .. py:method:: setup(parameterization) Numpy precomputation once the FFD block exists (``parameterization.ffd``, ``parameterization.geometry``). .. !! processed by numpydoc !! .. py:attribute:: name :value: 'layer' .. py:attribute:: principal_dim :value: None .. py:class:: WingShape(planform=None, sections=None, sweep_chord_fraction=0.25, semi_span=None, derived=None, fit_tol=0.001, scaler_mode='bounds', name='wing') Bases: :py:obj:`ShapeLayer` Planform and sectional design variables of a trapezoidal half wing. The wall must be a straight-tapered half wing with its root on the y = 0 symmetry plane, x chordwise and z up, inside a lofted FFD block whose parametric axes are (chord, span, vertical) -- the corner-list blocks of the wing drivers. planform : {name: (lower, upper)} for any of ``sweep`` sweep angle [rad] of the chord line at ``sweep_chord_fraction`` (0.25: quarter chord) ``aspect_ratio`` full-wing AR = b^2 / S ``root_chord`` ``span`` full-wing span b (twice the half wing's semi-span) ``taper_ratio`` tip chord / root chord Absolute values: each variable starts at the baseline wing's value and its bounds must bracket it. A name left out stays at its baseline, except the ``derived`` one. sections : {name: spec} for ``thickness`` and/or ``camber``. spec is the bounds, or a dict {'bounds': ..., 'modes': n} for n chordwise modes per FFD spanwise section (default 1). The variable then has n_sections x n entries, section-major (root first: all of the root's modes, then the next section's); bounds as for ``build_sectional_dv`` -- a scalar, a pair, or a pair of arrays of shape (n_sections,) (the same for every mode of a section), (n_sections, n) or (n_sections * n,). Pin an entry with (0., 0.). Deltas from the baseline, starting at zero, with xi the local chord fraction and B_m the Bernstein polynomials of degree n - 1: ``thickness`` relative change of the section's thickness (t/c), sum_m tau_m B_m(xi) -- 0.1 in every mode is 10% thicker ``camber`` change of camber over local chord, sum_m kappa_m 4 xi (1 - xi) B_m(xi), so the leading and trailing edge stay put; kappa in every mode is a parabolic camber line of maximum kappa With one mode these are a uniform thickness change and a parabolic camber line. The modes are least-squares fits on the block's chordwise basis; they are exact when it is a Bezier (n_chord = degree + 1) of degree >= n - 1 for thickness and >= n + 1 for camber (``describe`` reports the fit residual). derived : which of ``aspect_ratio``, ``root_chord`` and ``span`` follows from the other two and the taper ratio, s = AR c_r (1 + taper) / 4. Default: ``span``, unless span is a variable -- then whichever of aspect_ratio and root_chord is not (with only span a variable, name it). sweep_chord_fraction : chord line the sweep is measured on; sections scale about it, and its root point stays fixed. semi_span : baseline semi-span; default the wall's largest y (which includes any tip cap). fit_tol : allowed deviation of the baseline leading/trailing edges from straight lines, relative to the root chord. The geometry: FFD section j, at baseline span fraction eta_j = y_j / s0, moves to y_j' = eta_j s', its chord c_j' = c_r' (1 - (1 - taper') eta_j) scales it by r_j = c_j'/c_j0 about the reference chord line in x and about the chord plane in z (keeping t/c; the thickness modes act on top), and the reference line is placed at x_ref,root + y_j' tan(sweep'). These are lsdo_geo sectional stretches and translations; thickness and camber are additive sectional modes. With the taper ratio fixed the map is affine, so the deformed wall is exactly the trapezoid the variables describe; a taper change is exact at the FFD sections and B-spline-blended between them. .. !! processed by numpydoc !! .. py:method:: _derived_size(derived) .. py:method:: _fit_baseline(geometry) .. py:method:: _planform_variable(key) .. py:method:: _section_bounds(bounds, n1, modes) Broadcast per-section (n1,) or (n1, modes) bound arrays to the variable's flat (n1 * modes,) layout; scalars pass through. .. !! processed by numpydoc !! .. py:method:: _setup_sections(ffd) .. py:method:: declare(dvs) Create this layer's design variables in ``dvs`` (a ShapeDVSet). .. !! processed by numpydoc !! .. py:method:: describe() Lines for ``FFDShapeParameterization.print_design_variables``. .. !! processed by numpydoc !! .. py:method:: outputs() Planform quantities as csdl variables: sweep, aspect_ratio, root_chord, taper_ratio, tip_chord, semi_span, span, area (full wing) and section_chords (at the FFD sections). .. !! processed by numpydoc !! .. py:method:: sectional(sectional) Register sectional operations on ``sectional`` (a SectionalShape). .. !! processed by numpydoc !! .. py:method:: setup(parameterization) Numpy precomputation once the FFD block exists (``parameterization.ffd``, ``parameterization.geometry``). .. !! processed by numpydoc !! .. py:attribute:: PLANFORM_KEYS :value: ('sweep', 'aspect_ratio', 'root_chord', 'span', 'taper_ratio') .. py:attribute:: SECTION_KEYS :value: ('thickness', 'camber') .. py:attribute:: SIZE_KEYS :value: ('aspect_ratio', 'root_chord', 'span') .. py:attribute:: derived :value: 'span' .. py:attribute:: f .. py:attribute:: fit_tol :value: 0.001 .. py:attribute:: name :value: 'wing' .. py:attribute:: planform_spec .. py:attribute:: principal_dim :value: 1 .. py:attribute:: scaler_mode :value: 'bounds' .. py:attribute:: section_spec .. py:attribute:: semi_span_arg :value: None .. py:function:: _bernstein(degree, xi) Bernstein polynomials of `degree` at xi: shape (len(xi), degree + 1). .. !! processed by numpydoc !! .. py:function:: _bspline_basis(degree, n_coefficients, u) .. py:function:: _invert_monotone_bspline(degree, values, target) Parametric coordinate u in [0, 1] where the 1D B-spline with control values ``values`` equals ``target``. .. !! processed by numpydoc !! .. py:function:: _section_mode_profiles(kind, num_modes, xi) Chordwise profiles of the WingShape section modes at chord fractions xi: shape (len(xi), num_modes). Thickness: the Bernstein polynomials of degree num_modes - 1 (they sum to one: equal values scale the section uniformly). Camber: 4 xi (1 - xi) times those (zero at the leading and trailing edge; equal values give the parabolic camber line). .. !! processed by numpydoc !!