@@ -111,6 +111,72 @@ Where:
111111 Commonly the rocket's diameter is used as the reference length.
112112
113113
114+ Wind-frame and body-frame force coefficients
115+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
116+
117+ The three force coefficients above are given in the **aerodynamic (wind) frame **,
118+ relative to the velocity vector:
119+
120+ - :math: `C_L` (lift), :math: `C_Q` (side force) and :math: `C_D` (drag).
121+
122+ The same force can be expressed in the **body frame **, relative to the rocket's
123+ axes, which is what tools such as Missile DATCOM, wind tunnels and Barrowman
124+ report:
125+
126+ - :math: `C_N` (normal force, perpendicular to the body axis),
127+ - :math: `C_Y` (body side force),
128+ - :math: `C_A` (axial force, along the body axis).
129+
130+ The two sets are the same force in different frames, related by the
131+ angle-of-attack/sideslip rotation :math: `\mathbf {M}_{BA}`:
132+
133+ .. math ::
134+ \begin {aligned}
135+ C_N &= \cos \alpha \, C_L + \sin \alpha \,(\sin \beta \, C_Q + \cos \beta \, C_D) \\
136+ C_Y &= \cos \beta \, C_Q - \sin \beta \, C_D \\
137+ C_A &= -\sin \alpha \, C_L + \cos \alpha \,(\sin \beta \, C_Q + \cos \beta \, C_D)
138+ \end {aligned}
139+
140+ At small angles these reduce to :math: `C_N \approx C_L`, :math: `C_Y \approx C_Q`
141+ and :math: `C_A \approx C_D`.
142+
143+ Every aerodynamic surface exposes **all nine ** coefficients as attributes
144+ (``cL ``, ``cQ ``, ``cD ``, ``cN ``, ``cY ``, ``cA ``, ``cm ``, ``cn ``, ``cl ``). The
145+ coefficients you did not provide are computed on demand from the ones you did,
146+ so you can always read a surface's forces in whichever frame you need, for
147+ example ``surface.cN `` for the normal-force coefficient.
148+
149+ **Choosing the input frame. ** Because rocket aerodynamic data (DATCOM, wind
150+ tunnel, CFD, Barrowman) is usually reported in the body frame, you can supply
151+ your coefficients in either frame and RocketPy converts them for you. Provide the
152+ wind-frame names (``cL ``/``cQ ``/``cD ``) or the body-frame names
153+ (``cN ``/``cY ``/``cA ``); the moment coefficients (``cm ``/``cn ``/``cl ``) are the
154+ same in both.
155+
156+ Moment reference point
157+ ~~~~~~~~~~~~~~~~~~~~~~~~
158+
159+ The moment coefficients :math: `C_m`, :math: `C_n` and :math: `C_l` are taken about
160+ the surface's own reference point (its ``center_of_pressure ``). When the rocket
161+ assembles the total aerodynamic moment it transports each surface's force from
162+ that point to the rocket's **center of dry mass **, adding the
163+ :math: `\vec {r}_{\text {cp} \to \text {cdm}} \times \vec {F}` term, so the rocket's
164+ reported pitch/yaw moment and static margin are about the center of dry mass.
165+
166+ This matters when your coefficients come from a source that uses a different
167+ reference. Aerodynamic decks frequently give the pitch moment **about the nose
168+ tip ** (or another fixed station) rather than about the center of dry mass. A
169+ pitch-moment coefficient referenced to a point a distance :math: `d` ahead of the
170+ surface's center of pressure must be shifted before use:
171+
172+ .. math ::
173+ C_{m,\,\text {cp}} = C_{m,\,\text {ref}} + \frac {d}{L_{ref}}\, C_N
174+
175+ Provide the coefficient about the surface's center of pressure (or set
176+ ``center_of_pressure `` so the transport lands the moment at the intended point);
177+ otherwise the static margin will be off by the reference-point offset.
178+
179+
114180Aerodynamic angles
115181~~~~~~~~~~~~~~~~~~
116182
@@ -263,7 +329,18 @@ independent variables:
263329- ``yaw_rate ``: Yaw rate.
264330- ``roll_rate ``: Roll rate.
265331
266- The last column must be the coefficient value, and must contain a header,
332+ When the surface is created with ``unsteady_aero=True ``, the coefficients may
333+ additionally depend on the time derivatives of the flow angles, appended after
334+ ``roll_rate ``:
335+
336+ - ``alpha_dot ``: Rate of change of the angle of attack.
337+ - ``beta_dot ``: Rate of change of the side slip angle.
338+
339+ Callables must then accept the two extra trailing arguments
340+ (``coefficient(alpha, beta, Ma, Re, q, r, p, alpha_dot, beta_dot) ``) and
341+ ``.csv `` files may include ``alpha_dot ``/``beta_dot `` columns.
342+
343+ The last column must be the coefficient value, and must contain a header,
267344though the header name can be anything.
268345
269346.. important ::
@@ -451,3 +528,111 @@ shown below:
451528 rocket.add_surfaces(linear_generic_surface, position = (0 ,0 ,0 ))
452529
453530
531+ .. _generic_surface_interpolation :
532+
533+ Interpolation and Extrapolation of Tabulated Coefficients
534+ ---------------------------------------------------------
535+
536+ When a coefficient is provided as tabulated data (a ``.csv `` file or a list of
537+ points), RocketPy stores it as a :class: `rocketpy.Function ` and must decide two
538+ things: how to **interpolate ** *between * the tabulated points, and how to
539+ **extrapolate ** *outside * the tabulated range. Both :class: `rocketpy.GenericSurface `
540+ and :class: `rocketpy.LinearGenericSurface ` (and
541+ :class: `rocketpy.ControllableGenericSurface `) expose these as the
542+ ``interpolation `` and ``extrapolation `` arguments.
543+
544+ .. note ::
545+ Interpolation and extrapolation only apply to **tabulated ** coefficients.
546+ A coefficient given as a constant or a callable is evaluated directly, so
547+ these settings have no effect on it (a callable is assumed valid over its
548+ whole domain).
549+
550+ Each argument accepts either:
551+
552+ - a **single string **, applied to every coefficient of the surface; or
553+ - a **dictionary ** keyed by coefficient name, setting the method per
554+ coefficient. Coefficients omitted from the dictionary keep the default.
555+
556+ .. code-block :: python
557+
558+ from rocketpy import GenericSurface
559+
560+ radius = 0.0635
561+ generic_surface = GenericSurface(
562+ reference_area = np.pi * radius** 2 ,
563+ reference_length = 2 * radius,
564+ coefficients = {
565+ " cD" : " cD.csv" ,
566+ " cL" : " cL.csv" ,
567+ },
568+ # A single method applied to every coefficient:
569+ extrapolation = " constant" ,
570+ # ... or per coefficient (unlisted ones keep the default):
571+ interpolation = {" cD" : " linear" , " cL" : " akima" },
572+ )
573+
574+ Choosing an interpolation method
575+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
576+
577+ Interpolation controls the behavior *between * tabulated points. For 1-D tables
578+ the options are ``"linear" ``, ``"akima" ``, ``"spline" `` and ``"polynomial" ``.
579+
580+ - ``"linear" `` (**default **) is the safe choice. It never overshoots and
581+ introduces no spurious oscillations, which matters most across the
582+ **transonic drag rise ** (:math: `Ma \approx 0.8 `–:math: `1.2 `), where a spline
583+ will oscillate and invent non-physical wiggles in :math: `C_D`. Prefer it for
584+ coarse tables and for anything with a sharp feature.
585+ - ``"akima" `` gives continuous first derivatives (smoother
586+ :math: `C_{m_\alpha }`, cleaner stability curves) while resisting the overshoot
587+ of a natural cubic spline near kinks. It is the best "smooth" option for
588+ **dense, smooth ** data, such as lift/moment slopes in the attached-flow
589+ region.
590+ - ``"spline" `` produces the smoothest derivatives but overshoots near sharp
591+ features (stall, :math: `Ma = 1 `). Use it only for genuinely smooth,
592+ well-resolved data.
593+
594+ A practical rule of thumb: use ``"linear" `` against Mach (transonic kinks) and
595+ ``"akima" `` against angle of attack / sideslip when you have fine data and care
596+ about smooth derivatives.
597+
598+ .. note ::
599+ Multi-dimensional CSV tables that form a strict Cartesian grid are read with
600+ a :class: `scipy.interpolate.RegularGridInterpolator `. The ``interpolation ``
601+ argument still applies: it is mapped onto the interpolator's method, with
602+ ``"spline" `` becoming ``"cubic" `` and ``"akima" `` becoming the
603+ shape-preserving ``"pchip" `` (``"linear" `` stays linear). Smooth methods need
604+ enough samples per axis (``"cubic" `` needs at least 4), otherwise SciPy
605+ raises.
606+
607+ Choosing an extrapolation method
608+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
609+
610+ Extrapolation controls the behavior *outside * the tabulated range. The options
611+ are ``"constant" ``, ``"natural" `` and ``"zero" ``. This choice matters more than
612+ interpolation, because a bad one fails silently, precisely when the rocket is at
613+ an extreme condition beyond your data.
614+
615+ - ``"constant" `` holds the value at the nearest edge of the data. This is the
616+ **default for tabulated coefficients **, and the right choice for essentially
617+ all of them: a rocket can briefly exceed your tabulated Mach/angle range, and
618+ holding the last value is bounded and physically conservative.
619+ - ``"zero" `` returns 0 outside the range. Occasionally reasonable for force or
620+ moment *slopes * if you want contributions to vanish past the modeled envelope,
621+ but it introduces a discontinuity at the edge.
622+ - ``"natural" `` continues the fitted curve past the data. **Avoid this for
623+ tabulated coefficients **: extrapolating a linear or spline fit can send
624+ :math: `C_D` or a moment slope to large, non-physical values right when the
625+ rocket is at an extreme condition.
626+
627+ .. tip ::
628+ Tabulated coefficients default to ``extrapolation="constant" `` so they never
629+ run to non-physical values past the tabulated envelope. Override it only when
630+ you have a specific reason (e.g. ``"zero" `` to make a contribution vanish
631+ outside the modeled range).
632+
633+ .. seealso ::
634+ These arguments are forwarded to each :class: `rocketpy.Function `; see
635+ :meth: `rocketpy.Function.set_interpolation ` and
636+ :meth: `rocketpy.Function.set_extrapolation ` for the full list of methods.
637+
638+
0 commit comments