def convert(value, unit_from, unit_to): """ Convert values from one unit to another. This function accepts conversion with units of length, velocity, acceleration, angles, angle velocity, angle accelerations, density, pressure, absolute temperature, mass, force and energy. Parameters ---------- value : array_like Input values in original unit. unit_from : str Original unit as a string. unit_to : str Target unit as a string. Returns ------- value : array_like Input values converted into target unit. Notes ----- Use the source file as reference for accepted unit strings. Additional units and physical quantities can easily be added to this function. Examples -------- >>> convert(23, 'C', 'K') 296.14999999999998 >>> convert(sp.linspace(0, 30, num=5), 'm', 'ft') array([0, 24.60629921, 49.21259843, 73.81889764, 98.42519685]) """ itype, value = to_ndarray(value) #loop through all converters and search for pairs for cnvt in CNVTS: if unit_from in cnvt and unit_to in cnvt: #convert to SI unit if type(cnvt[unit_from]) is ListType: value_si = cnvt[unit_from][0](value) else: value_si = value * cnvt[unit_from] #convert to target unit if type(cnvt[unit_to]) is ListType: value = cnvt[unit_to][1](value_si) else: value = value_si / cnvt[unit_to] return from_ndarray(itype, value) #no converter found raise ValueError('Could not find definition of selected units.')
def convert(value, unit_from, unit_to): """ Convert values from one unit to another. This function accepts conversion with units of length, velocity, acceleration, angles, angle velocity, angle accelerations, density, pressure, absolute temperature, mass, force and energy. Parameters ---------- value : array_like Input values in original unit. unit_from : str Original unit as a string. unit_to : str Target unit as a string. Returns ------- value : array_like Input values converted into target unit. Notes ----- Use the source file as reference for accepted unit strings. Additional units and physical quantities can easily be added to this function. Examples -------- >>> convert(23, 'C', 'K') 296.14999999999998 >>> convert(sp.linspace(0, 30, num=5), 'm', 'ft') array([0, 24.60629921, 49.21259843, 73.81889764, 98.42519685]) """ itype, value = to_ndarray(value) # loop through all converters and search for pairs for cnvt in CNVTS: if unit_from in cnvt and unit_to in cnvt: # convert to SI unit if type(cnvt[unit_from]) == list: value_si = cnvt[unit_from][0](value) else: value_si = value * cnvt[unit_from] # convert to target unit if type(cnvt[unit_to]) == list: value = cnvt[unit_to][1](value_si) else: value = value_si / cnvt[unit_to] return from_ndarray(itype, value) # no converter found raise AerotbxValueError('Could not find definition of selected units.')
def geoidheight(lat, lon): """ Calculate geoid height using the EGM96 Geopotential Model. Parameters ---------- lat : array_like Lateral coordinates [degrees]. Values must be -90 <= lat <= 90. lon : array_like Longitudinal coordinates [degrees]. Values must be 0 <= lon <= 360. Returns ------- out : array_like Geoidheight [meters] Examples -------- >>> geoidheight(30, 20) 25.829999999999995 >>> geoidheight([30, 20],[40, 20]) [9.800000000000002, 14.43] """ global _EGM96 # convert the input value to array itype, lat = to_ndarray(lat) itype, lon = to_ndarray(lon) if lat.shape != lon.shape: raise AerotbxValueError("Inputs must contain equal number of values.") if (lat < -90).any() or (lat > 90).any() or not sp.isreal(lat).all(): raise AerotbxValueError("Lateral coordinates must be real numbers " "between -90 and 90 degrees.") if (lon < 0).any() or (lon > 360).any() or not sp.isreal(lon).all(): raise AerotbxValueError("Longitudinal coordinates must be real numbers " "between 0 and 360 degrees.") # if the model is not loaded, do so if _EGM96 is None: _EGM96 = _loadEGM96() # shift lateral values to the right reference and flatten coordinates lats = sp.deg2rad(-lat + 90).ravel() lons = sp.deg2rad(lon).ravel() # evaluate the spline and reshape the result evl = _EGM96.ev(lats, lons).reshape(lat.shape) return from_ndarray(itype, evl)
def geoidheight(lat, lon): """ Calculate geoid height using the EGM96 Geopotential Model. Parameters ---------- lat : array_like Lateral coordinates [degrees]. Values must be -90 <= lat <= 90. lon : array_like Longitudinal coordinates [degrees]. Values must be 0 <= lon <= 360. Returns ------- out : array_like Geoidheight [meters] Examples -------- >>> geoidheight(30, 20) 25.829999999999995 >>> geoidheight([30, 20],[40, 20]) [9.800000000000002, 14.43] """ global _EGM96 #convert the input value to array itype, lat = to_ndarray(lat) itype, lon = to_ndarray(lon) if lat.shape != lon.shape: raise Exception("Inputs must contain equal number of values.") if (lat < -90).any() or (lat > 90).any() or not sp.isreal(lat).all(): raise Exception("Lateral coordinates must be real numbers" \ " between -90 and 90 degrees.") if (lon < 0).any() or (lon > 360).any() or not sp.isreal(lon).all(): raise Exception("Longitudinal coordinates must be real numbers" \ " between 0 and 360 degrees.") #if the model is not loaded, do so if _EGM96 is None: _EGM96 = _loadEGM96() #shift lateral values to the right reference and flatten coordinates lats = sp.deg2rad(-lat + 90).ravel() lons = sp.deg2rad(lon).ravel() #evaluate the spline and reshape the result evl = _EGM96.ev(lats, lons).reshape(lat.shape) return from_ndarray(itype, evl)
def flowisentropic(**flow): """ Evaluate the isentropic relations with any flow variable. This function accepts a given set of specific heat ratios and a single input of isentropic flow variables. Inputs can be a single scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Mach number. Values must be greater than or equal to 0. T : array_like Temperature ratio T/T0. Values must be 0 <= T <= 1. P : array_like Pressure ratio P/P0. Values must be 0 <= P <= 1. rho : array_like Density ratio rho/rho0. Values must be 0 <= rho <= 1. sub : array_like Subsonic area ratio A/A*. Values must be greater than or equal to 1. sup : array_like Supersonic area ratio A/A*. Values must be greater than or equal to 1. Returns ------- out : (M, T, P, rho, area) Tuple of Mach number, temperature ratio, pressure ratio, density ratio and area ratio. Notes ----- This function accepts one and only one of the isentropic flow variables. It will raise an Exception when more than one input is given. Examples -------- >>> flowisentropic(M=3) (3.0, 0.35714285714285715, 0.027223683703862824, 0.076226314370815895, 4.2345679012345689) >>> flowisentropic(gamma=1.4, sup=1.6) (1.9352576078182122, 0.57174077399894296, 0.14131786852470815, 0.24717122680666009, 1.6000000000000001) >>> flowisentropic(T=sp.linspace(0, 1, 100)) (array, array, array, array, array) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations a = (gamma+1) / 2 b = (gamma-1) / 2 c = a / (gamma-1) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use the isentropic relations to solve for the mach number if mtype in ["mach", "m"]: if (flow < 0).any() or not sp.isreal(flow).all(): raise Exception("Mach number inputs must be real numbers" \ " greater than or equal to 0.") M = flow elif mtype in ["temp", "t"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Temperature ratio inputs must be real numbers" \ " 0 <= T <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt((1/b[flow != 0])*(flow[flow != 0]**(-1) - 1)) elif mtype in ["pres", "p"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Pressure ratio inputs must be real numbers" \ " 0 <= P <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \ (flow[flow != 0]**((gamma[flow != 0]-1)/-gamma[flow != 0]) - 1)) elif mtype in ["dens", "d", "rho"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Density ratio inputs must be real numbers" \ " 0 <= rho <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \ (flow[flow != 0]**((gamma[flow != 0]-1)/-1) - 1)) elif mtype in ["sub", "sup"]: if (flow < 1).any(): raise Exception("Area ratio inputs must be real numbers greater" \ " than or equal to 1.") M[:] = 0.2 if mtype == "sub" else 1.8 for _ in xrange(_AETB_iternum): K = M ** 2 f = -flow + a**(-c) * ((1+b*K)**c) / M #mach-area relation g = a**(-c) * ((1+b*K)**(c-1)) * (b*(2*c - 1)*K - 1) / K #deriv M = M - (f / g) #Newton-Raphson M[flow == 1] = 1 M[sp.isinf(flow)] = sp.inf else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") d = 1 + b*M**2 T = d**(-1) P = d**(-gamma/(gamma-1)) rho = d**(-1/(gamma-1)) area = sp.empty(M.shape, sp.float64) r = sp.logical_and(M != 0, sp.isfinite(M)) area[r] = a[r]**(-c[r]) * d[r]**c[r] / M[r] area[sp.logical_not(r)] = sp.inf return from_ndarray(itype, M, T, P, rho, area)
def flowprandtlmeyer(**flow): """ Prandtl-Meyer function for expansion waves. This function accepts a given set of specific heat ratios and an input of either Mach number, Mach angle or Prandtl-Meyer angle. Inputs can be a single scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Mach number. Values must be greater than or equal to 1. nu : array_like Prandtl-Meyer angle [degrees]. Values must be 0 <= M <= 90*(sqrt((g+1)/(g-1))-1). mu : array_like Mach angle [degrees]. Values must be 0 <= M <= 90. Returns ------- out : (M, nu, mu) Tuple of Mach number, Prandtl-Meyer angle, Mach angle. Examples -------- >>> flowprandtlmeyer(M=5) (5.0, 76.920215508538789, 11.536959032815489) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations l = sp.sqrt((gamma-1)/(gamma+1)) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use prandtl-meyer relation to solve for the mach number if mtype in ["mach", "m"]: if (flow < 1).any(): raise Exception("Mach number inputs must be real numbers greater" \ " than or equal to 1.") M = flow elif mtype in ["mu", "machangle"]: if (flow < 0).any() or (flow > 90).any(): raise Exception("Mach angle inputs must be real numbers" \ " 0 <= M <= 90.") M = 1 / sp.sin(sp.deg2rad(flow)) elif mtype in ["nu", "pm", "pmangle"]: if (flow < 0).any() or (flow > 90*((1/l)-1)).any(): raise Exception("Prandtl-Meyer angle inputs must be real" \ " numbers 0 <= M <= 90*(sqrt((g+1)/(g-1))-1).") M[:] = 2 #initial guess for the solution for _ in xrange(_AETB_iternum): b = sp.sqrt(M**2 - 1) f = -sp.deg2rad(flow) + (1/l) * sp.arctan(l*b) - sp.arctan(b) g = b*(1 - l**2) / (M*(1 + (l**2)*(b**2))) #derivative M = M - (f / g) #Newton-Raphson else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") #normal shock relations b = sp.sqrt(M**2 - 1) V = (1/l) * sp.arctan(l*b) - sp.arctan(b) U = sp.arcsin(1 / M) return from_ndarray(itype, M, sp.rad2deg(V), sp.rad2deg(U))
def flownormalshock(**flow): """ Evaluate the normal shock wave relations with any flow variable. This function accepts a given set of specific heat ratios and a single input of normal shock wave flow variables. Inputs can be a scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Upstream Mach number. Values must be greater than or equal to 1. M2 : array_like Downstream Mach number. Values must be SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1. T : array_like Temperature ratio T2/T1. Values must be greater than or equal to 1. P : array_like Pressure ratio P2/P1. Values must be greater than or equal to 1. rho : array_like Density ratio rho2/rho1. Values must be 1 <= rho <= (GAMMA+1)/(GAMMA-1). P0 : array_like Total pressure ratio P02/P01. Values must be 0 <= P0 <= 1. Pitot : array_like Rayleigh-Pitot ratio P02/P1. Values must be greater than or equal to ((GAMMA+1)/2)**(-GAMMA/(GAMMA+1)). Returns ------- out : (M, M2, T, P, rho, P0, Pitot) Tuple of upstream Mach number, downstream Mach number, temperature ratio, pressure ratio, density ratio, total pressure ratio, rayleigh-pitot ratio. Notes ----- This function accepts one and only one of the normal shock flow variables. It will raise an Exception when more than one input is given. Examples -------- >>> flownormalshock(M=2) (2.0, 0.57735026918962573, 1.6874999999999998, 4.5, 2.666666666666667, 0.72087386148474542, 5.640440812823317) >>> flownormalshock(gamma=1.4, Pitot=3.4) (1.4964833298836788, 0.70233741753226209, 1.3178766042516246, 2.4460394160563674, 1.8560458605647578, 0.93089743233402389, 3.3999999999999977) >>> flownormalshock(M2=[0.5, 0.6, 0.7]) (list, list, list, list, list, list, list) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations a = (gamma+1) / 2 b = (gamma-1) / 2 c = gamma / (gamma-1) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use the normal shock relations to solve for the mach number if mtype in ["mach", "m1", "m"]: if (flow < 1).any(): raise Exception("Mach number inputs must be real numbers" \ " greater than or equal to 1.") M = flow elif mtype in ["down", "mach2", "m2", "md"]: lowerbound = sp.sqrt((gamma - 1)/(2*gamma)) if (flow < lowerbound).any() or (flow > 1).any(): raise Exception("Mach number downstream inputs must be real" \ " numbers SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.") M[flow <= lowerbound] = sp.inf M[flow > lowerbound] = sp.sqrt((1 + b*flow**2) / (gamma*flow**2 - b)) elif mtype in ["pres", "p"]: if (flow < 1).any() or not sp.isreal(flow).all(): raise Exception("Pressure ratio inputs must be real numbers" \ " greater than or equal to 1.") M = sp.sqrt(((flow-1)*(gamma+1)/(2*gamma)) + 1) elif mtype in ["dens", "d", "rho"]: upperbound = (gamma+1) / (gamma-1) if (flow < 1).any() or (flow > upperbound).any(): raise Exception("Density ratio inputs must be real numbers" \ " 1 <= rho <= (GAMMA+1)/(GAMMA-1).") M[flow >= upperbound] = sp.inf M[flow < upperbound] = sp.sqrt(2*flow / (1 + gamma + flow - flow*gamma)) elif mtype in ["temp", "t"]: if (flow < 1).any(): raise Exception("Temperature ratio inputs must be real numbers" \ " greater than or equal to 1.") B = b + gamma/a - gamma*b/a - flow*a M = sp.sqrt((-B + sp.sqrt(B**2 - \ 4*b*gamma*(1-gamma/a)/a)) / (2*gamma*b/a)) elif mtype in ["totalp", "p0"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Total pressure ratio inputs must be real" \ " numbers 0 <= P0 <= 1.") M[:] = 2.0 #initial guess for the solution for _ in xrange(_AETB_iternum): f = -flow + (1 + (gamma/a)*(M**2 - 1))**(1-c) \ * (a*M**2 / (1 + b*M**2))**c g = 2*M*(a*M**2 / (b*M**2 + 1))**(c-1)*((gamma* \ (M**2-1))/a + 1)**(-c)*(a*c + gamma*(-c*(b*M**4 + 1) \ + b*M**4 + M**2)) / (b*M**2 + 1)**2 M = M - (f / g) #Newton-Raphson M[flow == 0] = sp.inf elif mtype in ["pito", "pitot", "rp"]: lowerbound = a**c if (flow < lowerbound).any(): raise Exception("Rayleigh-Pitot ratio inputs must be real" \ " numbers greater than or equal to ((G+1)/2)**(-G/(G+1)).") M[:] = 5.0 #initial guess for the solution K = a**(2*c - 1) for _ in xrange(_AETB_iternum): f = -flow + K * M**(2*c) / (gamma*M**2 - b)**(c-1) #Rayleigh-Pitot g = 2*K*M**(2*c - 1) * (gamma*M**2 - b)**(-c) * (gamma*M**2 - b*c) M = M - (f / g) #Newton-Raphson else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") #normal shock relations M2 = sp.sqrt((1 + b*M**2) / (gamma*M**2 - b)) rho = ((gamma+1)*M**2) / (2 + (gamma-1)*M**2) P = 1 + (M**2 - 1)*gamma / a T = P / rho P0 = P**(1-c) * rho**(c) P1 = a**(2*c - 1) * M**(2*c) / (gamma*M**2 - b)**(c-1) #handle infinite mach M2[M == sp.inf] = sp.sqrt((gamma[M == sp.inf] - 1)/(2*gamma[M == sp.inf])) T[M == sp.inf] = sp.inf P[M == sp.inf] = sp.inf rho[M == sp.inf] = (gamma[M == sp.inf]+1) / (gamma[M == sp.inf]-1) P0[M == sp.inf] = 0 P1[M == sp.inf] = sp.inf return from_ndarray(itype, M, M2, T, P, rho, P0, P1)
def stdatmos(**altitude): """ Evaluate the standard atmosphere at any given altitude. This function allows input of a single variable to calculate the atmospheric properties at different altitudes. The function can work with different types of standard models. The default model values are set as defined by the International Standard Atmosphere. Parameters ---------- model : dict, optional A standard atmosphere model as obtained from stdmodel. Partial models are allowed. The remaining model values default to the International Standard Atmosphere. h or geom : array_like Geometrical altitude [meters]. geop : array_like Geopotential altitude [meters]. abs : array_like Absolute altitude [meters]. T : array_like Temperature altitude [K]. P : array_like Pressure altitude [Pa]. rho : array_like Density altitude [kg/m^3]. Returns ------- out : (h, T, P, rho, a) Tuple of geometrical altitude, temperature, pressure, density and speed of sound at given altitudes. Notes ----- This function assumes a continues lapserate below 0 altitude and above the top layer, which allows for extrapolation outside the specified region (0 to 86km in ISA). Temperature altitude is obtained as the first altitude from 0 where the specified temperature exists. See Also -------- stdmodel Examples -------- >>> stdatmos(P=[1e5, 1e4, 1e3])[0] [110.8864127251899, 16221.007939493587, 31207.084373790043] >>> stdatmos(h=sp.linspace(-2000, 81000)) (array, array, array, array, array) """ # pop atmospherical model from input model = altitude.pop("model", { }) # check if model is a dictionary if type(model) != dict: raise AerotbxValueError("Custom atmosphere model is incompatible.") # check if a single remaining input exists if len(altitude) is not 1: raise TypeError("Function needs exactly one altitude input.") # pop the altitude input mtype, alt = altitude.popitem() # check if the altitude input type is valid if mtype not in ["h", "geom", "geop", "abs", "T", "P", "rho"]: raise AerotbxValueError("The altitude input should be a valid input type.") # convert the input to numpy arrays itype, alt = to_ndarray(alt) # model values R = model.get("R", 287.053) # gas constant [J/kg/K] (air) gamma = model.get("gamma", 1.4) # specific heat ratio [-] (air) g = model.get("g0", 9.80665) # gravity [m/s^2] (earth) radius = model.get("radius", 6356766.0) # earth radius [m] (earth) Tb = model.get("T0", 288.15) # base temperature [K] Pb = model.get("P0", 101325.0) # base pressure [Pa] # model lapse rate and height layers Hb = sp.array([0, 11, 20, 32, 47, 51, 71, sp.inf], sp.float64) * 1000 Lr = sp.array([-6.5, 0, 1, 2.8, 0, -2.8, -2], sp.float64) * 0.001 Hb = model.get("layers", Hb) # layer height [km] Lr = model.get("lapserate", Lr) # lapse rate [K/km] # preshape solution arrays T = sp.ones(alt.shape, sp.float64) * sp.nan P = sp.ones(alt.shape, sp.float64) * sp.nan # define the height array if mtype in ["h", "geom"]: h = alt * radius / (radius + alt) elif mtype is "geop": h = alt elif mtype is "abs": h = alt - radius else: h = sp.ones(alt.shape, sp.float64) * sp.nan for lr, hb, ht in zip(Lr, Hb[:-1], Hb[1:]): # calculate the temperature at layer top Tt = Tb + lr * (ht - hb) if mtype is "T": # break the loop if there are no nans in the solution array if not sp.isnan(h).any(): break # select all temperatures in current layer if lr == 0: sel = (alt == Tb) else: s = sp.sign(lr) bot = -sp.inf if hb == 0 else Tb * s top = sp.inf if ht == Hb[-1] else Tt * s sel = sp.logical_and(alt * s >= bot, alt * s < top) # only select when not already solved sel = sp.logical_and(sel, sp.isnan(h)) # temperature is given as input T[sel] = alt[sel] # solve for height and pressure if lr == 0: h[sel] = hb P[sel] = Pb else: h[sel] = hb + (1.0 / lr) * (T[sel] - Tb) P[sel] = Pb * (T[sel] / Tb) ** (-g / (lr * R)) elif mtype in ["P", "rho"]: # choose base value as pressure or density vb = Pb if mtype is "P" else Pb / (R * Tb) # select all input values below given pressure or density sel = alt <= (sp.inf if hb == 0 else vb) # break if nothing is selected if not sel.any(): break # solve for temperature and height if lr == 0: T[sel] = Tb h[sel] = hb - sp.log(alt[sel] / vb) * R * Tb / g else: x = g if mtype is "P" else (lr * R + g) T[sel] = Tb * (alt[sel] / vb) ** (-lr * R / x) h[sel] = hb + (T[sel] - Tb) / lr # pressure is given as input P[sel] = alt[sel] if mtype is "P" else alt[sel] * R * T[sel] else: # select all height values above layer base sel = h >= (-sp.inf if hb == 0 else hb) # break if nothing is selected if not sel.any(): break # solve for temperature and pressure if lr == 0: T[sel] = Tb P[sel] = Pb * sp.exp((-g / (R * Tb)) * (h[sel] - hb)) else: T[sel] = Tb + lr * (h[sel] - hb) P[sel] = Pb * (T[sel] / Tb) ** (-g / (lr * R)) # update pressure base value if lr == 0: Pb *= sp.exp((-g / (R * Tb)) * (ht - hb)) else: Pb *= (Tt / Tb) ** (-g / (lr * R)) # update temperature base value Tb = Tt # convert geopotential altitude to geometrical altitude h *= radius / (radius - h) # density rho = P / (R * T) # speed of sound a = sp.sqrt(gamma * R * T) return from_ndarray(itype, h, T, P, rho, a)
def stdatmos(**altitude): """ Evaluate the standard atmosphere at any given altitude. This function allows input of a single variable to calculate the atmospheric properties at different altitudes. The function can work with different types of standard models. The default model values are set as defined by the International Standard Atmosphere. Parameters ---------- model : dict, optional A standard atmosphere model as obtained from stdmodel. Partial models are allowed. The remaining model values default to the International Standard Atmosphere. h or geom : array_like Geometrical altitude [meters]. geop : array_like Geopotential altitude [meters]. abs : array_like Absolute altitude [meters]. T : array_like Temperature altitude [K]. P : array_like Pressure altitude [Pa]. rho : array_like Density altitude [kg/m^3]. Returns ------- out : (h, T, P, rho, a) Tuple of geometrical altitude, temperature, pressure, density and speed of sound at given altitudes. Notes ----- This function assumes a continues lapserate below 0 altitude and above the top layer, which allows for extrapolation outside the specified region (0 to 86km in ISA). Temperature altitude is obtained as the first altitude from 0 where the specified temperature exists. See Also -------- stdmodel Examples -------- >>> stdatmos(P=[1e5, 1e4, 1e3])[0] [110.8864127251899, 16221.007939493587, 31207.084373790043] >>> stdatmos(h=sp.linspace(-2000, 81000)) (array, array, array, array, array) """ #pop atmospherical model from input model = altitude.pop("model", {}) #check if model is a dictionary if type(model) is not DictType: raise Exception("Custom atmosphere model is incompatible.") #check if a single remaining input exists if len(altitude) is not 1: raise Exception("Function needs exactly one altitude input.") #pop the altitude input mtype, alt = altitude.popitem() #check if the altitude input type is valid if mtype not in ["h", "geom", "geop", "abs", "T", "P", "rho"]: raise Exception("The altitude input should be a valid input type.") #convert the input to numpy arrays itype, alt = to_ndarray(alt) #model values R = model.get("R", 287.053) #gas constant [J/kg/K] (air) gamma = model.get("gamma", 1.4) #specific heat ratio [-] (air) g = model.get("g0", 9.80665) #gravity [m/s^2] (earth) radius = model.get("radius", 6356766.0) #earth radius [m] (earth) Tb = model.get("T0", 288.15) #base temperature [K] Pb = model.get("P0", 101325.0) #base pressure [Pa] #model lapse rate and height layers Hb = sp.array([0, 11, 20, 32, 47, 51, 71, sp.inf], sp.float64) * 1000 Lr = sp.array([-6.5, 0, 1, 2.8, 0, -2.8, -2], sp.float64) * 0.001 Hb = model.get("layers", Hb) #layer height [km] Lr = model.get("lapserate", Lr) #lapse rate [K/km] #preshape solution arrays T = sp.ones(alt.shape, sp.float64) * sp.nan P = sp.ones(alt.shape, sp.float64) * sp.nan #define the height array if mtype in ["h", "geom"]: h = alt * radius / (radius + alt) elif mtype is "geop": h = alt elif mtype is "abs": h = alt - radius else: h = sp.ones(alt.shape, sp.float64) * sp.nan for lr, hb, ht in zip(Lr, Hb[:-1], Hb[1:]): #calculate the temperature at layer top Tt = Tb + lr * (ht - hb) if mtype is "T": #break the loop if there are no nans in the solution array if not sp.isnan(h).any(): break #select all temperatures in current layer if lr == 0: sel = (alt == Tb) else: s = sp.sign(lr) bot = -sp.inf if hb == 0 else Tb * s top = sp.inf if ht == Hb[-1] else Tt * s sel = sp.logical_and(alt * s >= bot, alt * s < top) #only select when not already solved sel = sp.logical_and(sel, sp.isnan(h)) #temperature is given as input T[sel] = alt[sel] #solve for height and pressure if lr == 0: h[sel] = hb P[sel] = Pb else: h[sel] = hb + (1.0 / lr) * (T[sel] - Tb) P[sel] = Pb * (T[sel] / Tb)**(-g / (lr * R)) elif mtype in ["P", "rho"]: #choose base value as pressure or density vb = Pb if mtype is "P" else Pb / (R * Tb) #select all input values below given pressure or density sel = alt <= (sp.inf if hb == 0 else vb) #break if nothing is selected if not sel.any(): break #solve for temperature and height if lr == 0: T[sel] = Tb h[sel] = hb - sp.log(alt[sel] / vb) * R * Tb / g else: x = g if mtype is "P" else (lr * R + g) T[sel] = Tb * (alt[sel] / vb)**(-lr * R / x) h[sel] = hb + (T[sel] - Tb) / lr #pressure is given as input P[sel] = alt[sel] if mtype is "P" else alt[sel] * R * T[sel] else: #select all height values above layer base sel = h >= (-sp.inf if hb == 0 else hb) #break if nothing is selected if not sel.any(): break #solve for temperature and pressure if lr == 0: T[sel] = Tb P[sel] = Pb * sp.exp((-g / (R * Tb)) * (h[sel] - hb)) else: T[sel] = Tb + lr * (h[sel] - hb) P[sel] = Pb * (T[sel] / Tb)**(-g / (lr * R)) #update pressure base value if lr == 0: Pb *= sp.exp((-g / (R * Tb)) * (ht - hb)) else: Pb *= (Tt / Tb)**(-g / (lr * R)) #update temperature base value Tb = Tt #convert geopotential altitude to geometrical altitude h *= radius / (radius - h) #density rho = P / (R * T) #speed of sound a = sp.sqrt(gamma * R * T) return from_ndarray(itype, h, T, P, rho, a)
def flowisentropic(**flow): """ Evaluate the isentropic relations with any flow variable. This function accepts a given set of specific heat ratios and a single input of isentropic flow variables. Inputs can be a single scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Mach number. Values must be greater than or equal to 0. T : array_like Temperature ratio T/T0. Values must be 0 <= T <= 1. P : array_like Pressure ratio P/P0. Values must be 0 <= P <= 1. rho : array_like Density ratio rho/rho0. Values must be 0 <= rho <= 1. sub : array_like Subsonic area ratio A/A*. Values must be greater than or equal to 1. sup : array_like Supersonic area ratio A/A*. Values must be greater than or equal to 1. Returns ------- out : (M, T, P, rho, area) Tuple of Mach number, temperature ratio, pressure ratio, density ratio and area ratio. Notes ----- This function accepts one and only one of the isentropic flow variables. It will raise an Exception when more than one input is given. Examples -------- >>> flowisentropic(M=3) (3.0, 0.35714285714285715, 0.027223683703862824, 0.076226314370815895, 4.2345679012345689) >>> flowisentropic(gamma=1.4, sup=1.6) (1.9352576078182122, 0.57174077399894296, 0.14131786852470815, 0.24717122680666009, 1.6000000000000001) >>> flowisentropic(T=sp.linspace(0, 1, 100)) (array, array, array, array, array) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations a = (gamma + 1) / 2 b = (gamma - 1) / 2 c = a / (gamma - 1) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use the isentropic relations to solve for the mach number if mtype in ["mach", "m"]: if (flow < 0).any() or not sp.isreal(flow).all(): raise Exception("Mach number inputs must be real numbers" \ " greater than or equal to 0.") M = flow elif mtype in ["temp", "t"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Temperature ratio inputs must be real numbers" \ " 0 <= T <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt( (1 / b[flow != 0]) * (flow[flow != 0]**(-1) - 1)) elif mtype in ["pres", "p"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Pressure ratio inputs must be real numbers" \ " 0 <= P <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \ (flow[flow != 0]**((gamma[flow != 0]-1)/-gamma[flow != 0]) - 1)) elif mtype in ["dens", "d", "rho"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Density ratio inputs must be real numbers" \ " 0 <= rho <= 1.") M[flow == 0] = sp.inf M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \ (flow[flow != 0]**((gamma[flow != 0]-1)/-1) - 1)) elif mtype in ["sub", "sup"]: if (flow < 1).any(): raise Exception("Area ratio inputs must be real numbers greater" \ " than or equal to 1.") M[:] = 0.2 if mtype == "sub" else 1.8 for _ in xrange(_AETB_iternum): K = M**2 f = -flow + a**(-c) * ((1 + b * K)**c) / M #mach-area relation g = a**(-c) * ( (1 + b * K)**(c - 1)) * (b * (2 * c - 1) * K - 1) / K #deriv M = M - (f / g) #Newton-Raphson M[flow == 1] = 1 M[sp.isinf(flow)] = sp.inf else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") d = 1 + b * M**2 T = d**(-1) P = d**(-gamma / (gamma - 1)) rho = d**(-1 / (gamma - 1)) area = sp.empty(M.shape, sp.float64) r = sp.logical_and(M != 0, sp.isfinite(M)) area[r] = a[r]**(-c[r]) * d[r]**c[r] / M[r] area[sp.logical_not(r)] = sp.inf return from_ndarray(itype, M, T, P, rho, area)
def flowprandtlmeyer(**flow): """ Prandtl-Meyer function for expansion waves. This function accepts a given set of specific heat ratios and an input of either Mach number, Mach angle or Prandtl-Meyer angle. Inputs can be a single scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Mach number. Values must be greater than or equal to 1. nu : array_like Prandtl-Meyer angle [degrees]. Values must be 0 <= M <= 90*(sqrt((g+1)/(g-1))-1). mu : array_like Mach angle [degrees]. Values must be 0 <= M <= 90. Returns ------- out : (M, nu, mu) Tuple of Mach number, Prandtl-Meyer angle, Mach angle. Examples -------- >>> flowprandtlmeyer(M=5) (5.0, 76.920215508538789, 11.536959032815489) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations l = sp.sqrt((gamma - 1) / (gamma + 1)) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use prandtl-meyer relation to solve for the mach number if mtype in ["mach", "m"]: if (flow < 1).any(): raise Exception("Mach number inputs must be real numbers greater" \ " than or equal to 1.") M = flow elif mtype in ["mu", "machangle"]: if (flow < 0).any() or (flow > 90).any(): raise Exception("Mach angle inputs must be real numbers" \ " 0 <= M <= 90.") M = 1 / sp.sin(sp.deg2rad(flow)) elif mtype in ["nu", "pm", "pmangle"]: if (flow < 0).any() or (flow > 90 * ((1 / l) - 1)).any(): raise Exception("Prandtl-Meyer angle inputs must be real" \ " numbers 0 <= M <= 90*(sqrt((g+1)/(g-1))-1).") M[:] = 2 #initial guess for the solution for _ in xrange(_AETB_iternum): b = sp.sqrt(M**2 - 1) f = -sp.deg2rad(flow) + (1 / l) * sp.arctan(l * b) - sp.arctan(b) g = b * (1 - l**2) / (M * (1 + (l**2) * (b**2))) #derivative M = M - (f / g) #Newton-Raphson else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") #normal shock relations b = sp.sqrt(M**2 - 1) V = (1 / l) * sp.arctan(l * b) - sp.arctan(b) U = sp.arcsin(1 / M) return from_ndarray(itype, M, sp.rad2deg(V), sp.rad2deg(U))
def flownormalshock(**flow): """ Evaluate the normal shock wave relations with any flow variable. This function accepts a given set of specific heat ratios and a single input of normal shock wave flow variables. Inputs can be a scalar or an array_like data structure. Parameters ---------- gamma : array_like, optional Specific heat ratio. Values must be greater than 1. M : array_like Upstream Mach number. Values must be greater than or equal to 1. M2 : array_like Downstream Mach number. Values must be SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1. T : array_like Temperature ratio T2/T1. Values must be greater than or equal to 1. P : array_like Pressure ratio P2/P1. Values must be greater than or equal to 1. rho : array_like Density ratio rho2/rho1. Values must be 1 <= rho <= (GAMMA+1)/(GAMMA-1). P0 : array_like Total pressure ratio P02/P01. Values must be 0 <= P0 <= 1. Pitot : array_like Rayleigh-Pitot ratio P02/P1. Values must be greater than or equal to ((GAMMA+1)/2)**(-GAMMA/(GAMMA+1)). Returns ------- out : (M, M2, T, P, rho, P0, Pitot) Tuple of upstream Mach number, downstream Mach number, temperature ratio, pressure ratio, density ratio, total pressure ratio, rayleigh-pitot ratio. Notes ----- This function accepts one and only one of the normal shock flow variables. It will raise an Exception when more than one input is given. Examples -------- >>> flownormalshock(M=2) (2.0, 0.57735026918962573, 1.6874999999999998, 4.5, 2.666666666666667, 0.72087386148474542, 5.640440812823317) >>> flownormalshock(gamma=1.4, Pitot=3.4) (1.4964833298836788, 0.70233741753226209, 1.3178766042516246, 2.4460394160563674, 1.8560458605647578, 0.93089743233402389, 3.3999999999999977) >>> flownormalshock(M2=[0.5, 0.6, 0.7]) (list, list, list, list, list, list, list) """ #parse the input gamma, flow, mtype, itype = _flowinput(flow) #calculate gamma-ratios for use in the equations a = (gamma + 1) / 2 b = (gamma - 1) / 2 c = gamma / (gamma - 1) #preshape mach array M = sp.empty(flow.shape, sp.float64) #use the normal shock relations to solve for the mach number if mtype in ["mach", "m1", "m"]: if (flow < 1).any(): raise Exception("Mach number inputs must be real numbers" \ " greater than or equal to 1.") M = flow elif mtype in ["down", "mach2", "m2", "md"]: lowerbound = sp.sqrt((gamma - 1) / (2 * gamma)) if (flow < lowerbound).any() or (flow > 1).any(): raise Exception("Mach number downstream inputs must be real" \ " numbers SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.") M[flow <= lowerbound] = sp.inf M[flow > lowerbound] = sp.sqrt( (1 + b * flow**2) / (gamma * flow**2 - b)) elif mtype in ["pres", "p"]: if (flow < 1).any() or not sp.isreal(flow).all(): raise Exception("Pressure ratio inputs must be real numbers" \ " greater than or equal to 1.") M = sp.sqrt(((flow - 1) * (gamma + 1) / (2 * gamma)) + 1) elif mtype in ["dens", "d", "rho"]: upperbound = (gamma + 1) / (gamma - 1) if (flow < 1).any() or (flow > upperbound).any(): raise Exception("Density ratio inputs must be real numbers" \ " 1 <= rho <= (GAMMA+1)/(GAMMA-1).") M[flow >= upperbound] = sp.inf M[flow < upperbound] = sp.sqrt(2 * flow / (1 + gamma + flow - flow * gamma)) elif mtype in ["temp", "t"]: if (flow < 1).any(): raise Exception("Temperature ratio inputs must be real numbers" \ " greater than or equal to 1.") B = b + gamma / a - gamma * b / a - flow * a M = sp.sqrt((-B + sp.sqrt(B**2 - \ 4*b*gamma*(1-gamma/a)/a)) / (2*gamma*b/a)) elif mtype in ["totalp", "p0"]: if (flow < 0).any() or (flow > 1).any(): raise Exception("Total pressure ratio inputs must be real" \ " numbers 0 <= P0 <= 1.") M[:] = 2.0 #initial guess for the solution for _ in xrange(_AETB_iternum): f = -flow + (1 + (gamma/a)*(M**2 - 1))**(1-c) \ * (a*M**2 / (1 + b*M**2))**c g = 2*M*(a*M**2 / (b*M**2 + 1))**(c-1)*((gamma* \ (M**2-1))/a + 1)**(-c)*(a*c + gamma*(-c*(b*M**4 + 1) \ + b*M**4 + M**2)) / (b*M**2 + 1)**2 M = M - (f / g) #Newton-Raphson M[flow == 0] = sp.inf elif mtype in ["pito", "pitot", "rp"]: lowerbound = a**c if (flow < lowerbound).any(): raise Exception("Rayleigh-Pitot ratio inputs must be real" \ " numbers greater than or equal to ((G+1)/2)**(-G/(G+1)).") M[:] = 5.0 #initial guess for the solution K = a**(2 * c - 1) for _ in xrange(_AETB_iternum): f = -flow + K * M**(2 * c) / (gamma * M**2 - b)**( c - 1) #Rayleigh-Pitot g = 2 * K * M**(2 * c - 1) * (gamma * M**2 - b)**(-c) * (gamma * M**2 - b * c) M = M - (f / g) #Newton-Raphson else: raise Exception("Keyword input must be an acceptable string to" \ " select input parameter.") #normal shock relations M2 = sp.sqrt((1 + b * M**2) / (gamma * M**2 - b)) rho = ((gamma + 1) * M**2) / (2 + (gamma - 1) * M**2) P = 1 + (M**2 - 1) * gamma / a T = P / rho P0 = P**(1 - c) * rho**(c) P1 = a**(2 * c - 1) * M**(2 * c) / (gamma * M**2 - b)**(c - 1) #handle infinite mach M2[M == sp.inf] = sp.sqrt( (gamma[M == sp.inf] - 1) / (2 * gamma[M == sp.inf])) T[M == sp.inf] = sp.inf P[M == sp.inf] = sp.inf rho[M == sp.inf] = (gamma[M == sp.inf] + 1) / (gamma[M == sp.inf] - 1) P0[M == sp.inf] = 0 P1[M == sp.inf] = sp.inf return from_ndarray(itype, M, M2, T, P, rho, P0, P1)