Source code for buffalo_wings.airfoil.internal.naca45_thickness

"""Classic and parametric NACA 4/5-digit thickness relations."""

import numpy as np

from buffalo_wings.internal.numeric import as_float_array
from buffalo_wings.type_aliases import FloatArray, FloatInput

from .thickness_base import (
    MAX_THICKNESS_INDEX_LIMIT,
    OPEN_TRAILING_EDGE_THICKNESS,
    Thickness,
)


[docs] class Naca45DigitThicknessClassic(Thickness): """ Class for the classic NACA 4-digit and 5-digit airfoil thickness. The thickness is parameterized on the square root of the chord location where the thickness is desired. This is to remove singularities that occur at the leading edge for the typical chord length parameterization. Notes ----- To obtain a classic thickness profile the maximum thickness should be set to an integer value, i.e. 12. However, any floating point value can be passed in, i.e. 12.3, if a more accurate maximum thickness is needed to be specified. """
[docs] def __init__(self, mti: float) -> None: """ Initialize a classic NACA thickness distribution. Parameters ---------- mti : float Maximum-thickness index as a percent of chord. Raises ------ ValueError If ``mti`` is outside the supported classic NACA range. """ self._a = ( np.array([0.29690, -0.12600, -0.35160, 0.28430, -0.10150]) / 0.20 ) self.max_thickness_index = mti
@property def max_thickness_index(self) -> float: """ Return the maximum-thickness index. Returns ------- float Maximum thickness as a percent-chord index. """ return 100 * self._tmax @max_thickness_index.setter def max_thickness_index(self, mti: float) -> None: if mti < 0 or mti >= MAX_THICKNESS_INDEX_LIMIT: raise ValueError(f"Invalid NACA 4/5-digit maximum thickness: {mti}") self._tmax = mti / 100.0 @property def a(self) -> FloatArray: """ Return the polynomial coefficients. Returns ------- FloatArray Coefficients for the classic thickness relation. """ return self._a
[docs] def discontinuities(self) -> list[float]: # noqa: PLR6301 """ Return the locations of any discontinuities in the thickness. Returns ------- List[float] Parametric coordinates of any discontinuities. """ return []
[docs] def max_thickness_parameter(self) -> float: # noqa: PLR6301 """ Return parameter coordinate of maximum thickness. Returns ------- float Parameter coordinate of maximum thickness. """ return np.sqrt(0.3)
[docs] def delta(self, t: FloatInput) -> FloatArray: """ Return the thickness at specified parameter location. Parameters ---------- t : numpy.ndarray Parameter location of interest. Equal to the square root of the desired chord location. Returns ------- numpy.ndarray Thickness at specified parameter. """ t = as_float_array(t) t2 = t**2 return ( self._tmax * t * ( self.a[0] + t * ( self.a[1] + t2 * (self.a[2] + t2 * (self.a[3] + t2 * self.a[4])) ) ) )
[docs] def delta_t(self, t: FloatInput) -> FloatArray: """ Return first derivative of thickness at specified parameter location. Parameters ---------- t : numpy.ndarray Parameter location of interest. Equal to the square root of the desired chord location. Returns ------- numpy.ndarray First derivative of thickness at specified parameter. """ t = as_float_array(t) t2 = t**2 return self._tmax * ( self.a[0] + 2 * t * ( self.a[1] + t2 * (2 * self.a[2] + t2 * (3 * self.a[3] + 4 * t2 * self.a[4])) ) )
[docs] def delta_tt(self, t: FloatInput) -> FloatArray: """ Return second derivative of thickness at specified parameter location. Parameters ---------- t : numpy.ndarray Parameter location of interest. Equal to the square root of the desired chord location. Returns ------- numpy.ndarray Second derivative of thickness at specified parameter. """ t = as_float_array(t) t2 = t**2 return ( 2 * self._tmax * ( self.a[1] + t2 * (6 * self.a[2] + t2 * (15 * self.a[3] + 28 * self.a[4] * t2)) ) )
[docs] class Naca45DigitThicknessParams(Naca45DigitThicknessClassic): """ Parametric NACA 4-digit and 5-digit airfoil thickness. The thickness is parameterized on the square root of the chord location where the thickness is desired. This is to remove singularities that occur at the leading edge for the typical chord length parameterization. This class extends the standard thickness distribution relation by solving for coefficients from the original constraints even for non-integer parameters, allowing a closed trailing edge, and allowing the leading-edge radius condition to be enforced directly. Notes ----- Specifying the same parameters as the classic thickness profile will not result in an identical thickness profile because the canonical coefficients do not match the stated constraints in the original source from Jacobs, Ward, and Pinkerton (1933). """
[docs] def __init__(self, mti: float, closed_te: bool, use_radius: bool) -> None: """ Initialize the parametric NACA thickness distribution. Parameters ---------- mti : float Maximum-thickness index as a percent of chord. closed_te : bool Whether the trailing edge closes to zero thickness. use_radius : bool Whether to enforce the classic leading-edge radius condition. """ super().__init__(mti=mti) self._closed_te = closed_te self._use_radius = use_radius self._calculate_a()
@property def closed_trailing_edge(self) -> bool: """ Return the trailing-edge closure flag. Returns ------- bool ``True`` when the trailing edge closes to zero thickness. """ return self._closed_te @closed_trailing_edge.setter def closed_trailing_edge(self, closed_te: bool) -> None: self._closed_te = closed_te self._calculate_a() @property def use_leading_edge_radius(self) -> bool: """ Return the leading-edge treatment flag. Returns ------- bool ``True`` when the classic leading-edge radius is enforced. """ return self._use_radius @use_leading_edge_radius.setter def use_leading_edge_radius(self, use_radius: bool) -> None: self._use_radius = use_radius self._calculate_a() def _calculate_a(self) -> None: """ Recompute the classic-thickness polynomial coefficients. Returns ------- None This method updates the stored coefficient array in place. """ # solve for new values of the coefficients using chord based # parameterization b_matrix = np.zeros([5, 5]) r = np.zeros([5, 1]) # first row is leading edge condition i = 0 if self._use_radius: b_matrix[i, :] = [1, 0, 0, 0, 0] r[i] = 0.29690 else: xi_1c = 0.1 t_1c = 0.078 b_matrix[i, :] = [ np.sqrt(xi_1c), xi_1c, xi_1c**2, xi_1c**3, xi_1c**4, ] r[i] = t_1c # second row is the maximum thickness at 0.3c i = 1 xi_max = 0.3 b_matrix[i, :] = [ 0.5 / np.sqrt(xi_max), 1, 2 * xi_max, 3 * xi_max**2, 4 * xi_max**3, ] r[i] = 0.0 # third row is the maximum thickness of 0.2c i = 2 t_max = 0.1 b_matrix[i, :] = [ np.sqrt(xi_max), xi_max, xi_max**2, xi_max**3, xi_max**4, ] r[i] = t_max # fourth row is trailing edge slope i = 3 te_slope = -0.234 b_matrix[i, :] = [0.5, 1, 2, 3, 4] r[i] = te_slope # fith row is trailing edge thickness i = 4 t_te = 0.0 if self._closed_te else OPEN_TRAILING_EDGE_THICKNESS b_matrix[i, :] = [1, 1, 1, 1, 1] r[i] = t_te self._a = (np.linalg.solve(b_matrix, r).transpose()[0]) / 0.20