def func(Asw_At_ratio): """ Args: Asw_At_ratio: Estimate of the critical area ratio between the Shock Wave area and the throat area. Return: The zero function "estimated ratio Pe/P0" - "Pe/P0". """ # Mach number just upstream of the shock wave Mup_sw = m_from_critical_area_ratio(Asw_At_ratio, "super", gamma) P1_P01_ratio = pressure_ratio(Mup_sw, gamma) # pressure ratio across the shock wave P2_P1_ratio = shockwave.pressure_ratio(Mup_sw, gamma) # Mach number just downstream of the shock wave Mdown_sw = shockwave.mach_downstream(Mup_sw, gamma) P02_P2_ratio = 1 / pressure_ratio(Mdown_sw) # critical area ratio just downstream of the shock wave Asw_A2s_ratio = critical_area_ratio(Mdown_sw, gamma) # critical area ratio at the exit (downstream of the shock wave) Ae_A2s_ratio = Ae_At_ratio / Asw_At_ratio * Asw_A2s_ratio # Mach number at the exit section Me = m_from_critical_area_ratio(Ae_A2s_ratio, "sub", gamma) Pe_P02_ratio = pressure_ratio(Me, gamma) estimated_Pe_P0_ratio = Pe_P02_ratio * P02_P2_ratio * P2_P1_ratio * P1_P01_ratio return estimated_Pe_P0_ratio - Pe_P0_ratio
def min_length_supersonic_nozzle_moc(ht, n, Me=None, A_ratio=None, gamma=1.4): """ Compute the contour of the minimum length supersonic nozzle in a planar case with the Method of characteristics. The method of characteristics provides a technique for properly designing the contour of a supersonic nozzle for shockfree, isentropic flow, taking into account the multidimensional flow inside the duct. Assumptions: - Planar case - Sharp corner at the throat, - M = 1 and theta = 0 at the throat. Parameters ---------- ht : float Throat height. Must be > 0. n : int Number of characteristic lines. Me : float Mach number at the exit section. Default to None. Must be > 1. If set to None, A_ratio must be provided. If both are set, Me will be used. A_ratio : float Ratio between the exit area and the throat area. Since this nozzle is planar, it is equivalent to Re/Rt. It must be > 1. Default to None. If set to None, Me must be provided. If both are set, Me will be used. gamma : float Specific heats ratio. Default to 1.4. Must be > 1. Returns ------- wall : array_like [2 x n+1] Coordinates of points on the nozzle's wall characteristics : list List of dictionaries. Each dictionary contains the keys "x", "y" for the coordinates of the points of each characteristic. Here, with characteristic, I mean the points of the right and left running characteristic. left_runn_chars : list List of dictionaries. Each dictionary contains the keys "Km", "Kp", "theta", "nu", "M", "mu", "x", "y". Each dictionary represents the points lying on the same left-running characteristic. theta_w_max : float Maximum wall inclination at the sharp corner. """ assert ht > 0, "The throat height must be a number > 0." # TODO: is n > 2 correct? assert n > 2, "The number of characteristic lines must be an integer > 2." assert gamma > 1, "Specific heats ratio must be > 1." if Me: assert Me > 1, "Exit Mach number must be > 1." elif A_ratio: assert A_ratio > 1, "Area ratio must be > 1." Me = m_from_critical_area_ratio(A_ratio, "super", gamma) else: raise ValueError("Either Me or A_ratio must be provided.") # Prandtl-Meyer function for the designed exit Mach number vme = prandtl_meyer_angle(Me, gamma) # max angle of the wall downstream of the throat (equation 11.33) theta_w_max = vme / 2 # read carefoully this: # http://www.ltas-aea.ulg.ac.be/cms/uploads/Aerothermodynamics05.pdf # especially slide 32 # TODO: is theta_1 small enough for very high n? theta_1 = 5e-02 # theta_1 = theta_w_max - np.floor(theta_w_max) delta_theta = (theta_w_max - theta_1) / (n - 1) ### ### Generate the grid ### # collection of left running characteristics. # each left running characteristic is composed by a certain number of # points, resulting from the intersection of this left running characteristic # with right running characteristics... left_runn_chars = [] for i in range(n): # number of points on the current left-running characteristic npt = n + 1 - i # init left_runn_chars.append({ "Km": np.zeros(npt), # right running characteristic K- "Kp": np.zeros(npt), # left running characteristic K+ "theta": np.zeros(npt), # deflection angle "nu": np.zeros(npt), # Prandtl-Meyer angle "M": np.zeros(npt), # Mach number "mu": np.zeros(npt), # Mach angle "x": np.zeros(npt), # x coordinate "y": np.zeros(npt), # y coordinate }) for j in range(npt - 1): # note that after the first line, theta_1 = 0 left_runn_chars[i]["theta"][j] = theta_1 + delta_theta * j if i == 0: left_runn_chars[i]["nu"][j] = left_runn_chars[i]["theta"][j] left_runn_chars[i]["Km"][j] = left_runn_chars[i]["theta"][ j] + left_runn_chars[i]["nu"][j] left_runn_chars[i]["Kp"][j] = left_runn_chars[i]["theta"][ j] - left_runn_chars[i]["nu"][j] else: left_runn_chars[i]["Km"][j] = left_runn_chars[i - 1]["Km"][j + 1] left_runn_chars[i]["nu"][j] = left_runn_chars[i]["Km"][ j] - left_runn_chars[i]["theta"][j] left_runn_chars[i]["Kp"][j] = left_runn_chars[i]["theta"][ j] - left_runn_chars[i]["nu"][j] left_runn_chars[i]["M"][j] = m_from_prandtl_meyer_angle( left_runn_chars[i]["nu"][j], gamma) left_runn_chars[i]["mu"][j] = mach_angle( left_runn_chars[i]["M"][j]) left_runn_chars[i]["theta"][j + 1] = left_runn_chars[i]["theta"][j] left_runn_chars[i]["nu"][j + 1] = left_runn_chars[i]["nu"][j] left_runn_chars[i]["Km"][j + 1] = left_runn_chars[i]["Km"][j] left_runn_chars[i]["Kp"][j + 1] = left_runn_chars[i]["Kp"][j] left_runn_chars[i]["M"][j + 1] = left_runn_chars[i]["M"][j] left_runn_chars[i]["mu"][j + 1] = left_runn_chars[i]["mu"][j] # after first line, we do not need this value anymore theta_1 = 0 ### ### Compute nodes coordinates ### # For readibility purposes, define tangent for angles in degrees def tand(angle): return np.tan(np.deg2rad(angle)) for i, l in enumerate(left_runn_chars): # number of points in the left running characteristic _n = len(l["theta"]) x = np.zeros(_n) y = np.zeros(_n) for j in range(_n): # the first characteristic is a special case, because at its left # there is only the point (0, 1) if i == 0: # point in the simmetry line if j == 0: x[j] = -1 / tand(l["theta"][j] - l["mu"][j]) y[j] = 0 # point at the wall elif j == _n - 1: num = y[j - 1] - 1 - x[j - 1] * tand( 0.5 * (l["theta"][j - 1] + l["mu"][j - 1] + l["theta"][j] + l["mu"][j])) den = tand(0.5 * (theta_w_max + l["theta"][j])) - tand( 0.5 * (l["theta"][j - 1] + l["mu"][j - 1] + l["theta"][j] + l["mu"][j])) x[j] = num / den y[j] = 1 + x[j] * tand(0.5 * (theta_w_max + l["theta"][j])) # points in the flow region else: num = ( 1 - y[j - 1] + x[j - 1] * tand(0.5 * (l["theta"][j - 1] + l["mu"][j - 1] + l["theta"][j] + l["mu"][j]))) den = tand( 0.5 * (l["theta"][j - 1] + l["mu"][j - 1] + l["theta"][j] + l["mu"][j])) - tand(l["theta"][j] - l["mu"][j]) x[j] = num / den y[j] = tand(l["theta"][j] - l["mu"][j]) * x[j] + 1 # all other left characteristics else: # previous left running characteristic line lprev = left_runn_chars[i - 1] # values of the point in the previous left running characteristic line x_prev = lprev["x"][j + 1] y_prev = lprev["y"][j + 1] theta_prev = lprev["theta"][j + 1] mu_prev = lprev["mu"][j + 1] # points in the simmetry line if j == 0: x[j] = x_prev - y_prev / (tand( 0.5 * (l["theta"][j] + theta_prev - l["mu"][j] - mu_prev))) y[j] = 0 # point at the wall elif j == _n - 1: num = x_prev * tand(0.5 * (theta_prev + l["theta"][j])) \ - y_prev + l["y"][j-1] - l["x"][j-1] * tand(0.5 * (l["theta"][j] + l["theta"][j-1] + l["mu"][j] + l["mu"][j-1])) den = tand(0.5 * (l["theta"][j] + theta_prev)) \ - tand(0.5 * (l["theta"][j-1] + l["theta"][j] + l["mu"][j-1] + l["mu"][j])) x[j] = num / den y[j] = y_prev + (l["x"][j] - x_prev) * tand( 0.5 * (theta_prev + l["theta"][j])) # points in the flow region else: s1 = tand(0.5 * (l["theta"][j] + l["theta"][j - 1] + l["mu"][j] + l["mu"][j - 1])) s2 = tand( 0.5 * (l["theta"][j] + theta_prev - l["mu"][j] - mu_prev)) x[j] = (y_prev - l["y"][j - 1] + s1 * l["x"][j - 1] - s2 * x_prev) / (s1 - s2) y[j] = l["y"][j - 1] + (l["x"][j] - l["x"][j - 1]) * s1 # add the computed coordinates points to the left running characteristic left_runn_chars[i]["x"] = x left_runn_chars[i]["y"] = y for l in left_runn_chars: l["x"] *= ht l["y"] *= ht # each symmetry line point has a left and right-running characteristic. # I pack them togheter for visualization purposes. characteristics = [] # extract the wall coordinates wall = np.zeros((n + 1, 2)) # first coordinate is the sharp corner wall[0, :] = [0, 1 * ht] for i, l in enumerate(left_runn_chars): # each characteristic starts from the sharp corner x = np.zeros(len(l["x"]) + i + 1) y = np.zeros(len(l["x"]) + i + 1) x[0] = 0 y[0] = 1 * ht # add the points of the current right-running characteristic. These points # are included in the previous left-running characteristics. for j in range(i): x[j + 1] = left_runn_chars[j]["x"][i - j] y[j + 1] = left_runn_chars[j]["y"][i - j] # add the point of the current left-running characteristic if i == 0: j = -1 x[j + 2:] = left_runn_chars[i]["x"] y[j + 2:] = left_runn_chars[i]["y"] characteristics.append({"x": x, "y": y}) wall[i + 1, :] = [l["x"][-1], l["y"][-1]] return wall, characteristics, left_runn_chars, theta_w_max
def compute(self, Pe_P0_ratio): """ Compute the flow quantities along the nozzle geometry. Parameters ---------- Pe_P0_ratio : float Back to Stagnation pressure ratio. The pressure at the exit plane coincide with the back pressure. Returns ------- L : np.ndarray Lengths along the stream flow. area_ratios : np.ndarray Area ratios along the stream flow. M : np.ndarray Mach numbers. P_ratios : np.ndarray Pressure ratios. rho_ratios : np.ndarray Density ratios. T_ratios : np.ndarray Temperature ratios. flow_condition : string The flow condition given the input pressure ratio. Asw_At_ratio : float Area ratio of the shock wave location if present, otherwise return None. """ self._flow_condition = self.flow_condition(Pe_P0_ratio) Ae = self._geometry.outlet_area At = self._geometry.critical_area Lc = self._geometry.length_convergent # copy the arrays: we do not want to modify the original geometry in case of # shock wave at the exit plane area_ratios = np.copy(self._geometry.area_ratio_array) L = np.copy(self._geometry.length_array) + Lc M = np.zeros_like(area_ratios) P_ratios = np.zeros_like(area_ratios) rho_ratios = np.zeros_like(area_ratios) T_ratios = np.zeros_like(area_ratios) flow_condition = self.flow_condition(Pe_P0_ratio) Asw_At_ratio = None if Pe_P0_ratio > 1: raise ValueError( "The back to reservoir pressure ratio must be Pe/P0 <= 1.") elif Pe_P0_ratio == 1: # no flow P_ratios += 1 T_ratios += 1 rho_ratios += 1 elif Pe_P0_ratio >= self._r1: # fully subsonic flow M = m_from_critical_area_ratio(area_ratios, "sub", self._gas.gamma) P_ratios = pressure_ratio(M, self._gas.gamma) rho_ratios = density_ratio(M, self._gas.gamma) T_ratios = temperature_ratio(M, self._gas.gamma) elif Pe_P0_ratio < self._r2: # fully supersonic flow in the divergent M[L < Lc] = m_from_critical_area_ratio(area_ratios[L < Lc], "sub", self._gas.gamma) M[L == Lc] = 1 M[L > Lc] = m_from_critical_area_ratio(area_ratios[L > Lc], "super", self._gas.gamma) P_ratios = pressure_ratio(M, self._gas.gamma) rho_ratios = density_ratio(M, self._gas.gamma) T_ratios = temperature_ratio(M, self._gas.gamma) elif Pe_P0_ratio == self._r2: # shock wave at the exit plane Ae_At_ratio = Ae / At Asw_At_ratio = Ae_At_ratio # Supersonic Mach number at the exit section just upstream of the shock wave Meup_sw = m_from_critical_area_ratio(Ae_At_ratio, "super", self._gas.gamma) # Subsonic Mach number at the exit section just downstream of the shock wave Medw_sw = shockwave.mach_downstream(Meup_sw, self._gas.gamma) # downstream of the shock wave there is a new isentropic critical area ratio Ae_A2s_ratio = critical_area_ratio(Medw_sw, self._gas.gamma) # total pressure ratio across the shock wave P02_P0_ratio = shockwave.total_pressure_ratio(Meup_sw) M[L < Lc] = m_from_critical_area_ratio(area_ratios[L < Lc], "sub", self._gas.gamma) M[L == Lc] = 1 M[L > Lc] = m_from_critical_area_ratio(area_ratios[L > Lc], "super", self._gas.gamma) # append the last subsonic point at the exit M = np.append( M, m_from_critical_area_ratio(Ae_A2s_ratio, "sub", self._gas.gamma)) P_ratios = pressure_ratio(M, self._gas.gamma) # For idx_after_sw (downstream of the shock wave), I've been returned P/P02. # Need to compute the ratio P/P0. P_ratios[-1] *= P02_P0_ratio rho_ratios = density_ratio(M, self._gas.gamma) # P02 = rho02 * R * T02 # P01 = rho01 * R * T01 # Taking the ratios, and knowing that T01 = T02 across the shock wave, leads to: # P02 / P01 = rho02 / rho01 # Need to compute the ratio rho/rho0 after the shock wave rho_ratios[-1] *= P02_P0_ratio T_ratios = temperature_ratio(M, self._gas.gamma) L = np.append(L, L[-1]) area_ratios = np.append(area_ratios, Ae_At_ratio) else: # shock into the divergent Ae_At_ratio = Ae / At # area ratio of the shock wave Asw_At_ratio = find_shockwave_area_ratio(Ae_At_ratio, Pe_P0_ratio, self._gas.R, self._gas.gamma) # Mach number at the exit section given the exit pressure ratio Pe_P0_ratio Me = m_from_critical_area_ratio_and_pressure_ratio( Ae_At_ratio, Pe_P0_ratio, self._gas.gamma) # downstream of the shock wave there is a new isentropic critical area ratio Ae_A2s_ratio = critical_area_ratio(Me, self._gas.gamma) # critical area downstream of the shock wave A2s = Ae / Ae_A2s_ratio # Mach number just upstream of the shock wave Mup_sw = m_from_critical_area_ratio(Asw_At_ratio, "super", self._gas.gamma) # total pressure ratio across the shock wave P02_P0_ratio = shockwave.total_pressure_ratio(Mup_sw) # find indeces before and after the shock wave in the divergent idx_before_sw = np.bitwise_and(L > Lc, area_ratios <= Asw_At_ratio) idx_after_sw = np.bitwise_and(L > Lc, area_ratios > Asw_At_ratio) # adjust the area ratios to use the new A2s downstream of the shock wave area_ratios[idx_after_sw] = area_ratios[idx_after_sw] * At / A2s # mach number in the convergent M[L < Lc] = m_from_critical_area_ratio(area_ratios[L < Lc], "sub", self._gas.gamma) M[L == Lc] = 1 # supersonic mach number M[idx_before_sw] = m_from_critical_area_ratio( area_ratios[idx_before_sw], "super", self._gas.gamma) # subsonic mach number M[idx_after_sw] = m_from_critical_area_ratio( area_ratios[idx_after_sw], "sub", self._gas.gamma) P_ratios = pressure_ratio(M, self._gas.gamma) # For idx_after_sw (downstream of the shock wave), I've been returned P/P02. # Need to compute the ratio P/P0. P_ratios[idx_after_sw] *= P02_P0_ratio rho_ratios = density_ratio(M, self._gas.gamma) # P02 = rho02 * R * T02 # P01 = rho01 * R * T01 # Taking the ratios, and knowing that T01 = T02 across the shock wave, leads to: # P02 / P01 = rho02 / rho01 # Need to compute the ratio rho/rho0 after the shock wave rho_ratios[idx_after_sw] *= P02_P0_ratio T_ratios = temperature_ratio(M, self._gas.gamma) L -= Lc return L, area_ratios, M, P_ratios, rho_ratios, T_ratios, flow_condition, Asw_At_ratio
def __init__(self, gas, geometry, input_state, Pb_P0_ratio=None): """ Parameters ---------- gas : Ideal_Gas Gas used in the nozzle. geometry : Nozzle_Geometry Nozzle geometry (lengths, areas, ...) input_state : Flow_State Represents the stagnation flow state. Pb_P0_ratio : float Back to Stagnation pressure ratio. Default to None. """ self._gas = gas self._geometry = geometry self._input_state = input_state R = gas.R gamma = gas.gamma T0 = input_state.total_temperature P0 = input_state.total_pressure rho0 = gas.solve(p=P0, t=T0) Ae_As_ratio = geometry.outlet_area / geometry.critical_area self._critical_temperature = temperature_ratio(1, gamma) * T0 self._critical_pressure = pressure_ratio(1, gamma) * P0 self._critical_density = density_ratio(1, gamma) * rho0 self._critical_velocity = sound_speed(self._critical_temperature, R, gamma) M2_sub = m_from_critical_area_ratio(Ae_As_ratio, "sub", gamma) M2_sup = m_from_critical_area_ratio(Ae_As_ratio, "super", gamma) # exit pressure ratio corresponding to fully subsonic flow in the divergent (when M=1 in A*) r1 = pressure_ratio(M2_sub, gamma) # exit pressure ratio corresponding to fully supersonic flow in the divergent # and Pe = Pb (pressure at back). Design condition. r3 = pressure_ratio(M2_sup, gamma) # isentropic pressure at the exit section of the divergent p_exit = r3 * P0 # pressure downstream of the normal shock wave at the exit section of the divergent p2 = shockwave.pressure_ratio(M2_sup, gamma) * p_exit # exit pressure ratio corresponding to a normal shock wave at the exit section of the divergent r2 = p2 / P0 self._r1 = r1 self._r2 = r2 self._r3 = r3 self._flow_condition = self.flow_condition(Pb_P0_ratio) self._output_state = None if Pb_P0_ratio: # compute output state _, _, M, pr, rhor, tr, _, _ = self.compute(Pb_P0_ratio) self._output_state = Flow_State(m=M[-1], p=pr[-1] * P0, rho=rhor[-1] * rho0, t=tr[-1] * T0, p0=Pb_P0_ratio * P0, t0=T0)
def isentropic_solver(param_name, param_value, gamma=1.4): """ Compute all isentropic ratios and Mach number given an input parameter. Parameters ---------- param_name : string Name of the parameter given in input. Can be either one of: 'm': Mach number 'pressure': Pressure Ratio P/P0 'density': Density Ratio rho/rho0 'temperature': Temperature Ratio T/T0 'crit_area_sub': Critical Area Ratio A/A* for subsonic case. 'crit_area_super': Critical Area Ratio A/A* for supersonic case. 'mach_angle': Mach Angle in degrees. 'prandtl_meyer': Prandtl-Meyer Angle in degrees. param_value : float/list/array_like Actual value of the parameter. If float, list, tuple is given as input, a conversion will be attempted. gamma : float Specific heats ratio. Default to 1.4. Must be > 1. Returns ------- M : array_like Mach number pr : array_like Pressure Ratio P/P0 dr : array_like Density Ratio rho/rho0 tr : array_like Temperature Ratio T/T0 prs : array_like Critical Pressure Ratio P/P* drs : array_like Critical Density Ratio rho/rho* trs : array_like Critical Temperature Ratio T/T* urs : array_like Critical Velocity Ratio U/U* ar : array_like Critical Area Ratio A/A* ma : array_like Mach Angle pm : array_like Prandtl-Meyer Angle """ assert isinstance(gamma, ( int, float)) and gamma > 1, "The specific heats ratio must be a number > 1." assert isinstance(param_name, str), "param_name must be a string" param_name = param_name.lower() assert param_name in [ 'm', 'pressure', 'density', 'temperature', 'crit_area_sub', 'crit_area_super', 'mach_angle', 'prandtl_meyer' ] # compute the Mach number param_value = convert_to_ndarray(param_value) M = None if param_name == "m": M = param_value assert np.all(M >= 0), "Mach number must be >= 0." elif param_name == "crit_area_sub": M = ise.m_from_critical_area_ratio(param_value, "sub", gamma) elif param_name == "crit_area_super": M = ise.m_from_critical_area_ratio(param_value, "super", gamma) func_dict = { 'pressure': ise.m_from_pressure_ratio, 'density': ise.m_from_density_ratio, 'temperature': ise.m_from_temperature_ratio, 'mach_angle': ise.m_from_mach_angle, 'prandtl_meyer': ise.m_from_prandtl_meyer_angle, } if param_name in func_dict.keys(): M = func_dict[param_name].__no_check(param_value, gamma) # compute the different ratios pr, dr, tr, prs, drs, trs, urs, ar, ma, pm = ise.get_ratios_from_mach.__no_check( M, gamma) return M, pr, dr, tr, prs, drs, trs, urs, ar, ma, pm