# compute_wing_induced_velocity.py
#
# Created: Dec 2020, E. Botero
# Modified: May 2021, E. Botero
# Jun 2021, E. Botero
# ----------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------
# package imports
import numpy as np
[docs]
def compute_wing_induced_velocity(VD,mach,compute_EW=False):
""" This computes the induced velocities at each control point of the vehicle vortex lattice
Assumptions:
Trailing vortex legs infinity are alligned to freestream
Outside of a call to the VLM() function itself, EW does not need to be computed, as C_mn
provides the same information in the body-frame.
Source:
1. Miranda, Luis R., Robert D. Elliot, and William M. Baker. "A generalized vortex
lattice method for subsonic and supersonic flow applications." (1977). (NASA CR)
2. VORLAX Source Code
Inputs:
VD - vehicle vortex distribution [Unitless]
mach [Unitless]
Outputs:
C_mn - total induced velocity matrix [Unitless]
s - semispan of the horshoe vortex [m]
t - tangent of the horshoe vortex [-]
CHORD - chord length for a panel [m]
RFLAG - sonic vortex flag [boolean]
ZETA - tangent incidence angle of the chordwise strip [-]
Properties Used:
N/A
"""
# unpack
LE_ind = VD.leading_edge_indices
TE_ind = VD.trailing_edge_indices
n_cp = VD.n_cp
n_mach = len(mach)
mach = np.array(mach,dtype=np.float32)
# Control points from the VLM
XAH = np.array(np.atleast_2d(VD.XAH*1.),dtype=np.float32)
YAH = np.array(np.atleast_2d(VD.YAH*1.),dtype=np.float32)
ZAH = np.array(np.atleast_2d(VD.ZAH*1.),dtype=np.float32)
XBH = np.array(np.atleast_2d(VD.XBH*1.),dtype=np.float32)
YBH = np.array(np.atleast_2d(VD.YBH*1.),dtype=np.float32)
ZBH = np.array(np.atleast_2d(VD.ZBH*1.),dtype=np.float32)
XA1 = np.array(np.atleast_2d(VD.XA1*1.),dtype=np.float32)
YA1 = np.array(np.atleast_2d(VD.YA1*1.),dtype=np.float32)
ZA1 = np.array(np.atleast_2d(VD.ZA1*1.),dtype=np.float32)
XB1 = np.array(np.atleast_2d(VD.XB1*1.),dtype=np.float32)
YB1 = np.array(np.atleast_2d(VD.YB1*1.),dtype=np.float32)
ZB1 = np.array(np.atleast_2d(VD.ZB1*1.),dtype=np.float32)
XA2 = np.array(np.atleast_2d(VD.XA2*1.),dtype=np.float32)
YA2 = np.array(np.atleast_2d(VD.YA2*1.),dtype=np.float32)
ZA2 = np.array(np.atleast_2d(VD.ZA2*1.),dtype=np.float32)
XB2 = np.array(np.atleast_2d(VD.XB2*1.),dtype=np.float32)
YB2 = np.array(np.atleast_2d(VD.YB2*1.),dtype=np.float32)
ZB2 = np.array(np.atleast_2d(VD.ZB2*1.),dtype=np.float32)
XC = np.array(np.atleast_2d(VD.XC*1.),dtype=np.float32)
YC = np.array(np.atleast_2d(VD.YC*1.),dtype=np.float32)
ZC = np.array(np.atleast_2d(VD.ZC*1.),dtype=np.float32)
XA_TE = np.array(np.atleast_2d(VD.XA_TE*1.),dtype=np.float32)
XB_TE = np.array(np.atleast_2d(VD.XB_TE*1.),dtype=np.float32)
# Panel Dihedral Angle, using AH and BH location
D = np.sqrt((YAH-YBH)**2+(ZAH-ZBH)**2)
COS_DL = (YBH-YAH)/D
DL = np.arccos(COS_DL)
DL[DL>np.pi/2] = DL[DL>np.pi/2] - np.pi # This flips the dihedral angle for the other side of the wing
# -------------------------------------------------------------------------------------------
# Compute velocity induced by horseshoe vortex segments on every control point by every panel
# -------------------------------------------------------------------------------------------
# If YBH is negative, flip A and B, ie negative side of the airplane. Vortex order flips
boolean = YAH>YBH
XA1[boolean], XB1[boolean] = XB1[boolean], XA1[boolean]
YA1[boolean], YB1[boolean] = YB1[boolean], YA1[boolean]
ZA1[boolean], ZB1[boolean] = ZB1[boolean], ZA1[boolean]
XA2[boolean], XB2[boolean] = XB2[boolean], XA2[boolean]
YA2[boolean], YB2[boolean] = YB2[boolean], YA2[boolean]
ZA2[boolean], ZB2[boolean] = ZB2[boolean], ZA2[boolean]
XAH[boolean], XBH[boolean] = XBH[boolean], XAH[boolean]
YAH[boolean], YBH[boolean] = YBH[boolean], YAH[boolean]
ZAH[boolean], ZBH[boolean] = ZBH[boolean], ZAH[boolean]
XA_TE[boolean], XB_TE[boolean] = XB_TE[boolean], XA_TE[boolean]
# These vortices will use AH and BH, rather than the typical location
xa = XAH
ya = YAH
za = ZAH
xb = XBH
yb = YBH
zb = ZBH
# This is not the control point for the panel, its the middle front of the vortex
xc = 0.5*(xa+xb)
yc = 0.5*(ya+yb)
zc = 0.5*(za+zb)
# This is the receiving point, or the control points
xo = XC.T
yo = YC.T
zo = ZC.T
# Incline the vortex
theta = np.arctan2(zb-za,yb-ya)
costheta = np.cos(theta)
sintheta = np.sin(theta)
# rotated axes
x1bar = (xb - xc)
y1bar = (yb - yc)*costheta + (zb - zc)*sintheta
xobar = (xo - xc)
yobar = (yo - yc)*costheta + (zo - zc)*sintheta
zobar =-(yo - yc)*sintheta + (zo - zc)*costheta
# COMPUTE COORDINATES OF RECEIVING POINT WITH RESPECT TO END POINTS OF SKEWED LEG.
shape = np.shape(xobar)
shape_0 = shape[0]
shape_1 = shape[1]
s = np.abs(y1bar)
t = x1bar/y1bar
s = np.repeat(s,shape_0,axis=0)
t = np.repeat(t,shape_0,axis=0)
X1 = xobar + t*s # In a planar case XC-XAH
Y1 = yobar + s # In a planar case YC-YAH
X2 = xobar - t*s # In a planar case XC-XBH
Y2 = yobar - s # In a planar case YC-YBH
# The cutoff hardcoded into vorlax
CUTOFF = 0.8
# CALCULATE AXIAL DISTANCE BETWEEN PROJECTION OF RECEIVING POINT ONTO HORSESHOE PLANE AND EXTENSION OF SKEWED LEG.
XTY = xobar - t*yobar
# The notation in this method is flipped from the paper
B2 = np.atleast_3d(mach**2-1.)
# SET VALUES OF NUMERICAL TOLERANCE CONSTANTS.
TOL = s /500.0
TOLSQ = TOL *TOL
TOLSQ2 = 2500.0 *TOLSQ
ZSQ = zobar *zobar
YSQ1 = Y1 *Y1
YSQ2 = Y2 *Y2
RTV1 = YSQ1 + ZSQ
RTV2 = YSQ2 + ZSQ
XSQ1 = X1 *X1
XSQ2 = X2 *X2
# Split the vectors into subsonic and supersonic
sub = (B2<0)[:,0,0]
B2_sub = B2[sub,:,:]
RO1_sub = B2_sub*RTV1
RO2_sub = B2_sub*RTV2
# ZERO-OUT PERTURBATION VELOCITY COMPONENTS
U = np.zeros((n_mach,shape_0,shape_1),dtype=np.float32)
V = np.zeros((n_mach,shape_0,shape_1),dtype=np.float32)
W = np.zeros((n_mach,shape_0,shape_1),dtype=np.float32)
if np.sum(sub)>0:
# COMPUTATION FOR SUBSONIC HORSESHOE VORTEX
U[sub], V[sub], W[sub] = subsonic(zobar,XSQ1,RO1_sub,XSQ2,RO2_sub,XTY,t,B2_sub,ZSQ,TOLSQ,X1,Y1,X2,Y2,RTV1,RTV2)
# COMPUTATION FOR SUPERSONIC HORSESHOE VORTEX. some values computed in a preprocessing section in VLM
sup = (B2>=0)[:,0,0]
B2_sup = B2[sup,:,:]
RO1_sup = B2[sup,:,:]*RTV1
RO2_sup = B2[sup,:,:]*RTV2
RNMAX = VD.panels_per_strip
CHORD = VD.chord_lengths
CHORD = np.repeat(CHORD,shape_0,axis=0)
RFLAG = np.ones((n_mach,shape_1),dtype=np.int8)
if np.sum(sup)>0:
U[sup], V[sup], W[sup], RFLAG[sup,:] = supersonic(zobar,XSQ1,RO1_sup,XSQ2,RO2_sup,XTY,t,B2_sup,ZSQ,TOLSQ,TOL,TOLSQ2,\
X1,Y1,X2,Y2,RTV1,RTV2,CUTOFF,CHORD,RNMAX,n_cp,TE_ind,LE_ind)
# Rotate into the vehicle frame and pack into a velocity matrix
C_mn = np.stack([U, V*costheta - W*sintheta, V*sintheta + W*costheta],axis=-1)
if compute_EW == True:
# Calculate the W velocity in the VORLAX frame for later calcs
# The angles are Dihedral angle of the current panel - dihedral angle of the influencing panel
COS1 = np.cos(DL.T - DL)
SIN1 = np.sin(DL.T - DL)
WEIGHT = 1
EW = (W*COS1-V*SIN1)*WEIGHT
else:
# Assume that this function is being used outside of VLM, EW is not needed
EW = np.nan
return C_mn, s, RFLAG, EW
[docs]
def subsonic(Z,XSQ1,RO1,XSQ2,RO2,XTY,T,B2,ZSQ,TOLSQ,X1,Y1,X2,Y2,RTV1,RTV2):
""" This computes the induced velocities at each control point
of the vehicle vortex lattice for subsonic mach numbers
Assumptions:
Trailing vortex legs infinity are alligned to freestream
Source:
1. Miranda, Luis R., Robert D. Elliot, and William M. Baker. "A generalized vortex
lattice method for subsonic and supersonic flow applications." (1977). (NASA CR)
2. VORLAX Source Code
Inputs:
Z Z relative location of the vortices [m]
XSQ1 X1 squared [m^2]
RO1 coefficient [-]
XSQ2 X2 squared [m^2]
RO2 coefficient [-]
XTY AXIAL DISTANCE BETWEEN PROJECTION OF RECEIVING POINT ONTO HORSESHOE PLANE AND EXTENSION OF SKEWED LEG [m]
T tangent of the horshoe vortex [-]
B2 mach^2-1 (-beta2) [-]
ZSQ Z squared [m^2]
TOLSQ coefficient [-]
X1 X coordinate of the left side of the vortex [m]
Y1 Y coordinate of the left side of the vortex [m]
X2 X coordinate of the right side of the vortex [m]
Y2 Y coordinate of the right side of the vortex [m]
RTV1 coefficient [-]
RTV2 coefficient [-]
Outputs:
U X velocity [unitless]
V Y velocity [unitless]
W Z velocity [unitless]
Properties Used:
N/A
"""
CPI = 4 * np.pi
RAD1 = np.sqrt(XSQ1 - RO1)
RAD2 = np.sqrt(XSQ2 - RO2)
TBZ = (T*T-B2)*ZSQ
DENOM = XTY * XTY + TBZ
TOLSQ = np.broadcast_to(TOLSQ,np.shape(DENOM))
DENOM[DENOM<TOLSQ] = TOLSQ[DENOM<TOLSQ]
FB1 = (T *X1 - B2 *Y1) /RAD1
FT1 = (X1 + RAD1) /(RAD1 *RTV1)
FT1[RTV1<TOLSQ] = 0.
FB2 = (T *X2 - B2 *Y2) /RAD2
FT2 = (X2 + RAD2) /(RAD2 *RTV2)
FT2[RTV2<TOLSQ] = 0.
QB = (FB1 - FB2) /DENOM
ZETAPI = Z /CPI
U = ZETAPI *QB
U[ZSQ<TOLSQ] = 0.
V = ZETAPI * (FT1 - FT2 - QB *T)
V[ZSQ<TOLSQ] = 0.
W = - (QB *XTY + FT1 *Y1 - FT2 *Y2) /CPI
return U, V, W
[docs]
def supersonic(Z,XSQ1,RO1,XSQ2,RO2,XTY,T,B2,ZSQ,TOLSQ,TOL,TOLSQ2,X1,Y1,X2,Y2,RTV1,RTV2,CUTOFF,CHORD,RNMAX,n_cp,TE_ind, LE_ind):
""" This computes the induced velocities at each control point
of the vehicle vortex lattice for supersonic mach numbers
Assumptions:
Trailing vortex legs infinity are alligned to freestream
Source:
1. Miranda, Luis R., Robert D. Elliot, and William M. Baker. "A generalized vortex
lattice method for subsonic and supersonic flow applications." (1977). (NASA CR)
2. VORLAX Source Code
Inputs:
Z Z relative location of the vortices [m]
XSQ1 X1 squared [m^2]
RO1 coefficient [-]
XSQ2 X2 squared [m^2]
RO2 coefficient [-]
XTY AXIAL DISTANCE BETWEEN PROJECTION OF RECEIVING POINT ONTO HORSESHOE PLANE AND EXTENSION OF SKEWED LEG [m]
T tangent of the horshoe vortex [-]
B2 mach^2-1 (-beta2) [-]
ZSQ Z squared [m^2]
TOLSQ coefficient [-]
X1 X coordinate of the left side of the vortex [m]
Y1 Y coordinate of the left side of the vortex [m]
X2 X coordinate of the right side of the vortex [m]
Y2 Y coordinate of the right side of the vortex [m]
RTV1 coefficient [-]
RTV2 coefficient [-]
CUTOFF coefficient [-]
CHORD chord length for a panel [m]
RNMAX number of chordwise panels [-]
n_cp number of control points [-]
TE_ind indices of the trailing edge [-]
LE_ind indices of the leading edge [-]
Outputs:
U X velocity [unitless]
V Y velocity [unitless]
W Z velocity [unitless]
RFLAG sonic vortex flag [boolean]
Properties Used:
N/A
"""
CPI = 2 * np.pi
T2 = T*T
ZETAPI = Z/CPI
shape = np.shape(RO1)
RAD1 = np.sqrt(XSQ1 - RO1)
RAD2 = np.sqrt(XSQ2 - RO2)
RAD1[np.isnan(RAD1)] = 0.
RAD2[np.isnan(RAD2)] = 0.
DENOM = XTY * XTY + (T2 - B2) *ZSQ # The last part of this is the TBZ term
SIGN = np.ones(shape,dtype=np.int8)
SIGN[DENOM<0] = -1.
TOLSQ = np.broadcast_to(TOLSQ,shape)
DENOM_COND = np.abs(DENOM)<TOLSQ
DENOM[DENOM_COND] = SIGN[DENOM_COND]*TOLSQ[DENOM_COND]
# Create a boolean for various conditions for F1 that goes to zero
bool1 = np.ones(shape,dtype=bool)
bool1[:,X1<TOL] = False
bool1[RAD1==0.] = False
RAD1[:,X1<TOL] = 0.0
REPS = CUTOFF*XSQ1
FRAD = RAD1
bool1[RO1>REPS] = False
FB1 = (T*X1-B2*Y1)/FRAD
FT1 = X1/(FRAD*RTV1)
FT1[RTV1<TOLSQ] = 0.
# Use the boolean to turn things off
FB1[np.isnan(FB1)] = 1.
FT1[np.isnan(FT1)] = 1.
FB1[np.isinf(FB1)] = 1.
FT1[np.isinf(FT1)] = 1.
FB1 = FB1*bool1
FT1 = FT1*bool1
# Round 2
# Create a boolean for various conditions for F2 that goes to zero
bool2 = np.ones(shape,dtype=bool)
bool2[:,X2<TOL] = False
bool2[RAD2==0.] = False
RAD2[:,X2<TOL] = 0.0
REPS = CUTOFF *XSQ2
FRAD = RAD2
bool2[RO2>REPS] = False
FB2 = (T *X2 - B2 *Y2)/FRAD
FT2 = X2 /(FRAD *RTV2)
FT2[RTV2<TOLSQ] = 0.
# Use the boolean to turn things off
FB2[np.isnan(FB2)] = 1.
FT2[np.isnan(FT2)] = 1.
FB2[np.isinf(FB2)] = 1.
FT2[np.isinf(FT2)] = 1.
FB2 = FB2*bool2
FT2 = FT2*bool2
QB = (FB1 - FB2) /DENOM
U = ZETAPI *QB
V = ZETAPI *(FT1 - FT2 - QB *T)
W = - (QB *XTY + FT1 *Y1 - FT2 *Y2) /CPI
# COMPUTATION FOR SUPERSONIC HORSESHOE VORTEX WHEN RECEIVING POINT IS IN THE PLANE OF THE HORSESHOE
in_plane = np.broadcast_to(ZSQ<TOLSQ2,shape)
RAD1_in = RAD1[in_plane]
RAD2_in = RAD2[in_plane]
Y1_in = Y1[ZSQ<TOLSQ2]
Y2_in = Y2[ZSQ<TOLSQ2]
XTY_in = XTY[ZSQ<TOLSQ2]
TOL_in = TOL[ZSQ<TOLSQ2]
if np.sum(in_plane)>0:
W_in = supersonic_in_plane(RAD1_in, RAD2_in, Y1_in, Y2_in, TOL_in, XTY_in, CPI)
else:
W_in = []
U[in_plane] = 0.
V[in_plane] = 0.
W[in_plane] = W_in
# DETERMINE IF TRANSVERSE VORTEX LEG OF HORSESHOE ASSOCIATED TO THE
# CONTROL POINT UNDER CONSIDERATION IS SONIC (SWEPT PARALLEL TO MACH
# LINE)? IF SO THEN RFLAG = 0.0, OTHERWISE RFLAG = 1.0.
size = shape[1]
n_mach = shape[0]
T2S = np.atleast_2d(T2[0,:])*np.ones((n_mach,1))
T2F = np.zeros((n_mach,size))
T2A = np.zeros((n_mach,size))
# Setup masks
F_mask = np.ones((n_mach,size),dtype=bool)
A_mask = np.ones((n_mach,size),dtype=bool)
F_mask[:,TE_ind] = False
A_mask[:,LE_ind] = False
# Apply the mask
T2F[A_mask] = T2S[F_mask]
T2A[F_mask] = T2S[A_mask]
# Zero out terms on the LE and TE
T2F[:,TE_ind] = 0.
T2A[:,LE_ind] = 0.
TRANS = (B2[:,:,0]-T2F)*(B2[:,:,0]-T2A)
RFLAG = np.ones((n_mach,size),dtype=np.int8)
RFLAG[TRANS<0] = 0.
FLAG_bool = np.zeros_like(TRANS,dtype=bool)
FLAG_bool[TRANS<0] = True
FLAG_bool = np.reshape(FLAG_bool,(n_mach,size,-1))
# COMPUTE THE GENERALIZED PRINCIPAL PART OF THE VORTEX-INDUCED VELOCITY INTEGRAL, WWAVE.
# FROM LINE 2647 VORLAX, the IR .NE. IRR means that we're looking at vortices that affect themselves
WWAVE = np.zeros(shape,dtype=np.float32)
COX = CHORD /RNMAX
eye = np.eye(n_cp,dtype=np.int8)
T2 = np.broadcast_to(T2,shape)*eye
B2_full = np.broadcast_to(B2,shape)*eye
COX = np.broadcast_to(COX,shape)*eye
WWAVE[B2_full>T2] = - 0.5 *np.sqrt(B2_full[B2_full>T2] -T2[B2_full>T2] )/COX[B2_full>T2]
W = W + WWAVE
# IF CONTROL POINT BELONGS TO A SONIC HORSESHOE VORTEX, AND THE
# SENDING ELEMENT IS SUCH HORSESHOE, THEN MODIFY THE NORMALWASH
# COEFFICIENTS IN SUCH A WAY THAT THE STRENGTH OF THE SONIC VORTEX
# WILL BE THE AVERAGE OF THE STRENGTHS OF THE HORSESHOES IMMEDIATELY
# IN FRONT OF AND BEHIND IT.
# Zero out the row
FLAG_bool_rep = np.broadcast_to(FLAG_bool,shape)
W[FLAG_bool_rep] = 0. # Default to zero
# The self velocity goes to 2
FLAG_bool_split = np.array(np.split(FLAG_bool.ravel(),n_mach))
FLAG_ind = np.array(np.where(FLAG_bool_split))
squares = np.zeros((size,size,n_mach))
squares[FLAG_ind[1],FLAG_ind[1],FLAG_ind[0]] = 1
squares = np.ravel(squares,order='F')
FLAG_bool_self = np.where(squares==1)[0]
W = W.ravel()
W[FLAG_bool_self] = 2. # It's own value, -2
# The panels before and after go to -1
FLAG_bool_bef = FLAG_bool_self - 1
FLAG_bool_aft = FLAG_bool_self + 1
W[FLAG_bool_bef] = -1.
W[FLAG_bool_aft] = -1.
W = np.reshape(W,shape)
return U, V, W, RFLAG
[docs]
def supersonic_in_plane(RAD1,RAD2,Y1,Y2,TOL,XTY,CPI):
""" This computes the induced velocities at each control point
in the special case where the vortices lie in the same plane
Assumptions:
Trailing vortex legs infinity are alligned to freestream
In plane vortices only produce W velocity
Source:
1. Miranda, Luis R., Robert D. Elliot, and William M. Baker. "A generalized vortex
lattice method for subsonic and supersonic flow applications." (1977). (NASA CR)
2. VORLAX Source Code
Inputs:
RAD1 array of zeros [-]
RAD2 array of zeros [-]
Y1 Y coordinate of the left side of the vortex [m]
Y2 Y coordinate of the right side of the vortex [m]
TOL coefficient [-]
XTY AXIAL DISTANCE BETWEEN PROJECTION OF RECEIVING POINT ONTO HORSESHOE PLANE AND EXTENSION OF SKEWED LEG [m]
CPI 2 Pi [radians]
Outputs:
W Z velocity [unitless]
Properties Used:
N/A
"""
shape = np.shape(RAD2)
F1 = np.zeros(shape)
F2 = np.zeros(shape)
reps = int(shape[0]/np.size(Y1))
Y1 = np.tile(Y1,reps)
Y2 = np.tile(Y2,reps)
TOL = np.tile(TOL,reps)
XTY = np.tile(XTY,reps)
F1[np.abs(Y1)>TOL] = RAD1[np.abs(Y1)>TOL]/Y1[np.abs(Y1)>TOL]
F2[np.abs(Y2)>TOL] = RAD2[np.abs(Y2)>TOL]/Y2[np.abs(Y2)>TOL]
W = np.zeros(shape)
W[np.abs(XTY)>TOL] = (-F1[np.abs(XTY)>TOL] + F2[np.abs(XTY)>TOL])/(XTY[np.abs(XTY)>TOL]*CPI)
return W