Source code for buffalo_wings.airfoil.internal.naca4_camber

"""NACA 4-digit airfoil camber relations."""

import numpy as np

from buffalo_wings.type_aliases import FloatArray, FloatScalar

from .camber_base import CLASSIC_NACA_DIGIT_MAX, Camber


[docs] class Naca4DigitCamber(Camber): """Camber for the NACA 4-digit airfoils."""
[docs] def __init__(self, mci: float, lci: float) -> None: """ Initialize a NACA 4-digit camber line from its designation. Parameters ---------- mci : float Maximum-camber index from the NACA designation. lci : float Maximum-camber-location index from the NACA designation. Raises ------ ValueError If either designation index is outside the supported range. """ # bootstrap values self._m: float = 2 self._p: float = 2 self.max_camber_index = mci self.loc_max_camber_index = lci
@property def max_camber_index(self) -> float: """ Return the maximum-camber index. Returns ------- float Maximum camber as a designation index. """ return 100.0 * self._m @max_camber_index.setter def max_camber_index(self, mci: float) -> None: """ Set the maximum-camber index from the NACA designation. Parameters ---------- mci : float Maximum-camber index from the NACA designation. Raises ------ ValueError If ``mci`` is outside the supported classic 4-digit range. """ if mci == 0: self._m = 0.0 self._p = 0.0 elif (mci < 0) or (mci > CLASSIC_NACA_DIGIT_MAX): raise ValueError(f"Invalid NACA 4-digit maximum camber: {mci}.") else: # check to see if currently no camber and force valid value if self._p == 0: self._p = 0.2 self._m = mci / 100.0 @property def loc_max_camber_index(self) -> float: """ Return the maximum-camber-location index. Returns ------- float Chordwise location of maximum camber as a designation index. """ return 10.0 * self._p @loc_max_camber_index.setter def loc_max_camber_index(self, lci: float) -> None: """ Set the max-camber location index from the NACA designation. Parameters ---------- lci : float Chordwise location of maximum camber as a designation index. Raises ------ ValueError If ``lci`` is outside the supported classic 4-digit range. """ if lci == 0: self._m = 0.0 self._p = 0.0 elif (lci < 0) or (lci > CLASSIC_NACA_DIGIT_MAX): raise ValueError( f"Invalid NACA 4-digit maximum camber location: {lci}." ) else: # check to see if currently no camber and force valid value if self._m == 0: self._m = 0.2 self._p = lci / 10.0
[docs] def joints(self) -> list[float]: """ Return the locations of any joints/discontinuities in the camber line. Returns ------- List[float] Xi-coordinates of any discontinuities. """ if (self._m == 0) or (self._p == 0): return [0.0, 1.0] return [0.0, self._p, 1.0]
[docs] def max_camber_parameter(self) -> FloatScalar: """ Return parameter where the camber is maximum. Returns ------- float Parameter where camber is maximum. """ return self._m
def _y(self, t: FloatArray) -> FloatArray: """ Return the camber location at specified chord location. Parameters ---------- t : numpy.ndarray Chord location of interest. Returns ------- numpy.ndarray Camber at specified point. """ if (self._m == 0) or (self._p == 0): return np.zeros_like(t) t = np.asarray(t, dtype=np.float64) def fore(t: FloatArray) -> FloatArray: return (self._m / self._p**2) * (2 * self._p * t - t**2) def aft(t: FloatArray) -> FloatArray: return (self._m / (1 - self._p) ** 2) * ( 1 + 2 * self._p * (t - 1) - t**2 ) return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft]) def _y_t(self, t: FloatArray) -> FloatArray: """ Return first derivative of camber at specified chord location. Parameters ---------- t : numpy.ndarray Chord location of interest. Returns ------- numpy.ndarray First derivative of camber at specified point. """ if (self._m == 0) or (self._p == 0): return np.zeros_like(t) t = np.asarray(t, dtype=np.float64) def fore(t: FloatArray) -> FloatArray: return 2 * (self._m / self._p**2) * (self._p - t) def aft(t: FloatArray) -> FloatArray: return 2 * (self._m / (1 - self._p) ** 2) * (self._p - t) return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft]) def _y_tt(self, t: FloatArray) -> FloatArray: """ Return second derivative of camber at specified chord location. Parameters ---------- t : numpy.ndarray Chord location of interest. Returns ------- numpy.ndarray Second derivative of camber at specified point. """ if (self._m == 0) or (self._p == 0): return np.zeros_like(t) t = np.asarray(t, dtype=np.float64) def fore(t: FloatArray) -> FloatArray: return -2 * (self._m / self._p**2) * np.ones_like(t) def aft(t: FloatArray) -> FloatArray: return -2 * (self._m / (1 - self._p) ** 2) * np.ones_like(t) return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft]) def _y_ttt(self, t: FloatArray) -> FloatArray: """ Return third derivative of camber at specified chord location. Parameters ---------- t : numpy.ndarray Chord location of interest. Returns ------- numpy.ndarray Third derivative of camber at specified point. """ if (self._m == 0) or (self._p == 0): return np.zeros_like(t) t = np.asarray(t, dtype=np.float64) def fore(t: FloatArray) -> FloatArray: return np.zeros_like(t) def aft(t: FloatArray) -> FloatArray: return np.zeros_like(t) return np.piecewise(t, [t <= self._p, t > self._p], [fore, aft])