"""
.. geom_ops
--------
Contains functions for modifying and creating airfoil geometries
"""
import numpy as np
from .. import sampling
from . import Error
[docs]
def generateNACA(code, nPts, spacingFunc=sampling.cosine, func_args=None):
"""
This function generates airfoil coordinates from the analytical definition of the NACA airfoils. It currently only supports 4 digit series airfoils.
Parameters
----------
code : str
The 4 digit code, this is expected to be a length four string
nPts : int
The number of points to sample from the defintion of the NACA airfoil, half will be sampled on the top and half on the bottom.
This must be an even number.
spacingFunc : callable
The spacing function to use for determining the sampling point locations of the x coordinates of the camber line
func_args : dict
Arguments to pass to the sampling function when it is called
Returns
-------
af : Ndarray [N,2]
Coordinates that were sampled from the NACA 4-digit code
"""
if len(code) != 4:
raise Error("Expected a NACA 4 digit code, but got %.d digits." % len(code))
if not code.isdigit():
raise Error("The NACA code provided was not made up of only digits.")
if not func_args:
func_args = {}
if nPts % 2 != 0:
raise ValueError("nPts must be an even number.")
camber_x = spacingFunc(0.0, 1.0, nPts // 2, **func_args)
camber_y = np.zeros_like(camber_x)
upper_x = np.zeros((nPts // 2, 1))
lower_x = np.zeros_like(upper_x)
upper_y = np.zeros_like(upper_x)
lower_y = np.zeros_like(upper_x)
m = int(code[0]) * 0.01
p = int(code[1]) * 0.1
t = int(code[2:]) * 0.01
for i in range(len(camber_x)):
if camber_x[i] < p:
camber_y[i] = m / p**2 * (2 * p * camber_x[i] - camber_x[i] ** 2)
else:
camber_y[i] = m / (1 - p) ** 2 * ((1 - 2 * p) + 2 * p * camber_x[i] - camber_x[i] ** 2)
for i in range(len(camber_x)):
thick_y = (
t
/ 0.2
* (
0.2969 * np.sqrt(camber_x[i])
- 0.126 * camber_x[i]
- 0.3516 * camber_x[i] ** 2
+ 0.2843 * camber_x[i] ** 3
- 0.1015 * camber_x[i] ** 4
)
)
if camber_x[i] < p:
theta = np.arctan(m / p**2 * (2 * p - 2 * camber_x[i]))
else:
theta = np.arctan(m / (1 - p) ** 2 * (2 * p - 2 * camber_x[i]))
upper_x[i] = camber_x[i] - thick_y * np.sin(theta)
lower_x[i] = camber_x[i] + thick_y * np.sin(theta)
upper_y[i] = camber_y[i] + thick_y * np.cos(theta)
lower_y[i] = camber_y[i] - thick_y * np.cos(theta)
coords = np.hstack(
(np.concatenate((np.flip(upper_x[1:]), lower_x)), np.concatenate((np.flip(upper_y[1:]), lower_y)))
)
return coords
[docs]
def checkCellRatio(X, ratio_tol=1.2):
"""
Checks a set of coordinates for consecutive cell ratios that exceed a given tolerance
Parameters
----------
X : Ndarray [N,2]
The set of coordinates being checked
ratio_tol : float
The maximum cell ratio that is allowed
Returns
-------
cell_ratio : Ndarray [N]
the cell ratios for each cell
max_cell_ratio : float
the maximum cell ratio
avg_cell_ratio : float
the average cell ratio
exc : Ndarray
the cell indicies that exceed the ratio tolerance
"""
X_diff = X[1:, :] - X[:-1, :]
cell_size = np.sqrt(X_diff[:, 0] ** 2 + X_diff[:, 1] ** 2)
crit_cell_size = np.flatnonzero(cell_size < 1e-10)
for i in crit_cell_size:
print("critical I", i)
cell_ratio = cell_size[1:] / cell_size[:-1]
exc = np.flatnonzero(cell_ratio > ratio_tol)
if exc.size > 0:
print("WARNING: There are ", exc.size, " elements which exceed " "suggested cell ratio: ", exc)
max_cell_ratio = np.max(cell_ratio, 0)
avg_cell_ratio = np.average(cell_ratio, 0)
print("Max cell ratio: ", max_cell_ratio)
print("Average cell ratio", avg_cell_ratio)
return cell_ratio, max_cell_ratio, avg_cell_ratio, exc
def _translateCoords(X, dX):
"""
Translates the input coordinates
Parameters
----------
X : Ndarray [N,2]
The x/y coordinate pairs that are being translated
dX : Ndarray [N,2]
The dx/dy amount to translate in each direction
Returns
-------
translated_X : Ndarray [N,2]
The translated coordinates
"""
return X + dX
def _rotateCoords(X, angle, origin):
"""
Rotates coordinates about the specified origin by angle (in deg)
Parameters
----------
X : Ndarray [N,2]
The x/y coordinate pairs that are being rotated
angle : float
The angle in radians to rotate the coordinates
origin : Ndarray [2]
The x/y coordinate pair specifying the rotation origin
Returns
-------
rotated_X : Ndarray[N,2]
The rotated coordinate pairs
"""
c, s = np.cos(angle), np.sin(angle)
R = np.array(((c, -s), (s, c)))
shifted_X = X - origin
shifted_rotated_X = np.dot(shifted_X, R.T)
rotated_X = shifted_rotated_X + origin
return rotated_X
def _scaleCoords(X, scale, origin):
"""
Scales coordinates in both dimensions by the scaling factor from a given origin
Parameters
----------
X : Ndarry [N,2]
The x/y coordinate pairs that are being scaled
scale : float
The scaling factor
origin : float
The location about which scaling occurs (This point will not change)
Returns
-------
scaled_X : Ndarray [N,2]
the scaled coordinate values
"""
shifted_X = X - origin
shifted_scaled_X = shifted_X * scale
scaled_X = shifted_scaled_X + origin
return scaled_X
def _getClosestY(coords, x):
"""
Gets the closest y value on the upper and lower point to an x value
Parameters
----------
coords : Ndarray [N,2]
coordinates defining the airfoil
x : float
The x value to find the closest point for
Returns
-------
yu : float
The y value of the closest coordinate on the upper surface
yl : float
The y value of the closest coordinate on the lower surface
"""
top = coords[: len(coords + 1) // 2 + 1, :]
bottom = coords[len(coords + 1) // 2 :, :]
x_top = np.ones(len(top))
for i in range(len(top)):
x_top[i] = abs(top[i, 0] - x)
yu = top[np.argmin(x_top), 1]
x_bottom = np.ones(len(bottom))
for i in range(len(bottom)):
x_bottom[i] = abs(bottom[i, 0] - x)
yl = bottom[np.argmin(x_bottom), 1]
return yu, yl