"""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])