def prepare_single_phase_liquid(T_inlet, steps, p_ref, m_dot, fp: FluidProperties): """ Prepare numpy arrays for calculating channel length in a liquid single-phase section of a channel. NOTE: This is done to avoid recalculating arrays that are not dependent on channel geometry, therefore speeding up optimizations.\ After all, during optimization the geometry is what varies.\ Also it also ensure that temperature endpoint and enthalpy cleanly match with saturation temperature in the correct phase Args: T_inlet (K): Inlet temperature steps (-): Amount steps of dT taken to reach saturation temperature T_sat (dT = (T_sat-T_inlet)/2) p_ref (Pa): Pressure assumed constant along channel, equal to inlet pressure m_dot (kg/s): Mass flow fp (FluidProperties): Object to access propellant properties with """ T_sat = fp.get_saturation_temperature(p=p_ref) # [K] Saturation temperature assert ( T_inlet < T_sat) # Check input assert (steps > 1) # Temperature and other intermediate variable in channel section i=0...n T, dT = np.linspace(start=T_inlet, stop=T_sat, num=steps,retstep=True) # [K] Temperature T_i (also returns steps between sections) # The reference temperature for heat transfer calculations # The first value [0] should not be important. The heat transfer calculated at i is between i-1 and i # So, from T[i-1] to T[i]. So, if there reference temperature is the average dT/2 must SUBTRACTED #T_ref = T - dT/2 # [K] Reference temperature for heat transfer calculations ## Get all thermodynamic values that can be precalculated # NOTE: all last values must be replaced with the correct values for the saturated liquid state # Before the values are replaced, sometimes an error is thrown because the values are close to the saturation point # That, or NaNs and infinites show up. This shouldn't be a problem, unless the second-to-last points also start getting close to the saturation point # Enthalpy h = fp.get_enthalpy(T=T, p=p_ref) # [J/kg] Enthalpy h[-1] = fp.get_saturation_enthalpy_liquid(p=p_ref) # [J/kg] Saturation enthalpy at T_n = T_sat # Heating power required in section to increase temp by dT. Use enthalpy difference delta_h = delta_enthalpy_per_section(h=h) # [J/kg] Enthalpy difference per section Q_dot = required_power(m_dot=m_dot, delta_h=delta_h) # [W] # Density rho = fp.get_density(T=T, p=p_ref) # [kg/m^3] Density rho[-1] = fp.get_liquid_density_at_psat(p_sat=p_ref) # [kg/m^3] Saturation density # Prandtl number Pr = fp.get_Prandtl(T=T, p=p_ref) # [-] Prandtl number Pr[-1] = fp.get_saturation_Prandtl_liquid(p_sat=p_ref) # [-] Saturation Prandtl # Thermal conductivity kappa = fp.get_thermal_conductivity(T=T, p=p_ref) # [W/(m*K)] Conductivity kappa[-1] = fp.get_liquid_saturation_conductivity(p_sat=p_ref) # [W/(m*K)] Saturation conductivity # Viscosity mu = fp.get_viscosity(T=T, p=p_ref) # [Pa*s] Viscosity mu[-1] = fp.get_liquid_saturation_viscosity(p_sat=p_ref) # [Pa*s] Saturation viscosity return {\ "T":T, # [K] "dT": dT, # [K] "rho": rho, # [kg/m^3] "h": h, # [J/kg] "Q_dot": Q_dot, # [W] "Pr": Pr, # [-] "kappa": kappa, # [W/(m*K)] "mu": mu, # [Pa*s] }
def test_table(self): # Table V from Lemmon2004 has density as inputs, which is not interesting for our purposes, so first the pressure must be obtained fp = FluidProperties("nitrogen") # T in, Density in mol/dm^3, viscosity out Pa*s in_out = ((100, 25.0, 79.7418e-6, 10), (200, 10.0, 21.0810e-6, 10), (300, 5.0, 20.7430e-6, 10)) molar_mass = 0.02801348 # [kg/mol] also from Span2000 for T, rho, mu, places in in_out: p = fp.get_pressure(T=T, rho=rho * molar_mass * 1e3) res_mu = fp.get_viscosity(T=T, p=p) self.assertAlmostEqual(res_mu, mu, places=places)
def prepare_single_phase_gas(T_outlet, steps, p_ref, m_dot, fp: FluidProperties): T_sat = fp.get_saturation_temperature(p=p_ref) # [K] Saturation temperature assert (T_outlet > T_sat) assert (steps > 1) # Temperature and other intermediate variable in channel section i=0...n T, dT = np.linspace(start=T_sat, stop=T_outlet, num=steps, retstep=True) # [K] Temperature T_i # The reference temperature for heat transfer calculations # The first value [0] should not be important. The heat transfer calculated at i is between i-1 and i # So, from T[i-1] to T[i]. So, if there reference temperature is the average dT/2 must SUBTRACTED #T_ref = T - dT/2 # [K] Reference temperature for heat transfer calculations ## Get all thermodynamic values that can be precalculated # NOTE: all first values must be replaced with the correct values for the saturated gas state # Before the values are replaced, sometimes an error is thrown because the values are close to the saturation point # That, or NaNs and infinites show up. This shouldn't be a problem, unless the second-to-last points also start getting close to the saturation point # Enthalpy h = fp.get_enthalpy(T=T, p=p_ref) # [J/kg] Enthalpy h[0] = fp.get_saturation_enthalpy_gas(p=p_ref) # [J/kg] Saturation enthalpy at T_n = T_sat # Heating power required in section to increase temp by dT. Use enthalpy difference delta_h = delta_enthalpy_per_section(h=h) # [J/kg] Enthalpy difference per section Q_dot = required_power(m_dot=m_dot, delta_h=delta_h) # [W] # Density rho = fp.get_density(T=T, p=p_ref) # [kg/m^3] Density rho[0] = fp.get_vapour_density_at_psat(p_sat=p_ref) # [kg/m^3] Saturation density # Prandtl number Pr = fp.get_Prandtl(T=T, p=p_ref) # [-] Prandtl number Pr[0] = fp.get_saturation_Prandtl_gas(p_sat=p_ref) # [-] Saturation Prandtl # Thermal conductivity kappa = fp.get_thermal_conductivity(T=T, p=p_ref) # [W/(m*K)] Conductivity kappa[0] = fp.get_gas_saturation_conductivity(p_sat=p_ref) # [W/(m*K)] Saturation conductivity # Viscosity mu = fp.get_viscosity(T=T, p=p_ref) # [Pa*s] Viscosity mu[0] = fp.get_gas_saturation_viscosity(p_sat=p_ref) # [Pa*s] Saturation viscosity return {\ "T":T, # [K] "dT": dT, # [K] "rho": rho, # [kg/m^3] "h": h, # [J/kg] "Q_dot": Q_dot, # [W] "Pr": Pr, # [-] "kappa": kappa, # [W/(m*K)] "mu": mu, # [Pa*s] }
def Rajeev_complete(p_chamber, T_chamber, w_throat, h_throat, throat_roc, AR_exit, p_back, divergence_half_angle, fp: FluidProperties, is_cold_flow): """ Function that implements all corrections proposed by Makhan2018 Args: p_chamber (Pa): Chamber pressure T_chamber (K): Chamber temperature w_throat (m): Throat width h_throat (m): Throat heigh (or channel depth) throat_roc (m): Throat radius of curvature AR_exit (-): Area ratio of nozzle exit area divided by throat area p_back (Pa): Back pressure divergence_half_angle (rad): Divergence half angle of nozzle fp (FluidProperties): Object to access fluid properties is_cold_flow (bool): Reynolds number is adjusted depending on whether the chamber is heated or cooled Raises: ValueError: Is raised for hot flow, since no verification is done yet on that equation """ # Get the (assumed to be) constant fluid properties gamma = fp.get_specific_heat_ratio(T=T_chamber, p=p_chamber) # [-] Specific heat ratio R = fp.get_specific_gas_constant() # [J/kg] Specific gas constant # Report calculated values for verification and comparison purposes print("Gamma: {:1.4f}".format(gamma)) print("R: {:3.2f} J/kg\n".format(R)) # Calculate basic peformance parameters A_throat = w_throat * h_throat # [m] Throat area ## IDEAL PERFORMANCE # First get ideal performance, and check if the nozzle is properly expanded. ep = IRT.get_engine_performance(p_chamber=p_chamber, T_chamber=T_chamber, A_throat=A_throat, AR_exit=AR_exit, p_back=p_back, gamma=gamma, R=R) # Report ideal performance print("Thrust: {:.2f} mN".format(ep['thrust'] * 1e3)) print("Isp_ideal: {:.1f} s".format(ep['thrust'] / ep['m_dot'] / 9.80655)) print("Mass flow: {:.3f} mg/s".format(ep['m_dot'] * 1e6)) m_dot_ideal = ep['m_dot'] # [kg/s] Ideal mass flow #F_ideal = ep['thrust'] # [N] Ideal thrust ## CALCULATING THE CORRECTION FACTORS # Calculate the divergence loss and report it CF_divergence_loss = divergence_loss_conical_2D( alpha=divergence_half_angle) print("\n -- DIVERGENCE LOSS for {:2.2f} deg divergence half-angle".format( math.degrees(divergence_half_angle))) print( " Divergence loss (2D concical): {:.5f} ".format(CF_divergence_loss)) # Calculate the viscous loss # To determine the Reynolds number at the throat, the hydraulic diameter at the throat and nozzle conditions must be determined # Get hydraulic diameter of the nozzle from the wetted perimeter and nozzle area wetted_perimeter_throat = 2 * (w_throat + h_throat ) # [m] Wetted perimeter throat Dh_throat = hydraulic_diameter(A=A_throat, wetted_perimeter=wetted_perimeter_throat ) # [m] Hydraulic diameter at throat p_throat = p_chamber / IRT.pressure_ratio( M=1, gamma=gamma) # [Pa] pressure in throat T_throat = T_chamber / IRT.temperature_ratio( M=1, gamma=gamma) # [K] Temperature in throat viscosity_throat = fp.get_viscosity(T=T_throat, p=p_throat) # Throat reynolds based on ideal mass flow? Re_throat = reynolds(m_dot=m_dot_ideal, A=A_throat, D_hydraulic=Dh_throat, viscosity=viscosity_throat) if is_cold_flow: Re_throat_wall = Reynolds_throat_wall_cold(reynolds_throat=Re_throat) else: Re_throat_wall = Reynolds_throat_wall_hot(reynolds_throat=Re_throat) print("\n-- THROAT CONDITIONS --") print(" p = {:2.4f} bar, T = {:4.2f} K".format( p_throat * 1e-5, T_throat)) print(" mu = {:2.4f} [microPa*s] Dh = {:3.4f} [microm]".format( viscosity_throat * 1e6, Dh_throat * 1e6)) print(" Reynolds: {:6.6f} ".format(Re_throat)) CF_viscous_loss = viscous_loss(area_ratio=AR_exit, reynolds_throat_wall=Re_throat_wall) print(" CF_viscous_loss: {:1.5f}".format(CF_viscous_loss)) # Calculating throat boundary layer loss, which causes a reduction in effective throat area/mass flow Cd_throat_boundary_loss = throat_boundary_loss(gamma=gamma, reynolds_throat=Re_throat, throat_radius=0.5 * Dh_throat, throat_roc=throat_roc) print("\n-- DISCHARGE FACTOR --") print(" Throat boundary layer: {:1.4f}".format(Cd_throat_boundary_loss)) ## APPLYING THE CORRECTION FACTORS # Now all these loss factors must be combined into a new "real" thrust # The divergence loss only applies to the jet/momentum thrust and not the pressure, so jet thrust is needed # This is equal to the exit velocity times corrected mass flow. The returned exit velocity does not include pressure terms! # First we must know the corrected mass flow m_dot_real = ep['m_dot'] * Cd_throat_boundary_loss # [kg/s] # Secondly, we must know the pressure thrust to add to the jet thrust again F_pressure = IRT.pressure_thrust(p_chamber=p_chamber, p_back=p_back, A_throat=A_throat, AR=AR_exit, gamma=gamma) F_divergence = m_dot_real * ep[ 'u_exit'] * CF_divergence_loss + F_pressure # [N] Thrust decreased by divergence loss, pressure term must be added again, since divergence only applies to jet thrust # This jet thrust is then again corrected by viscous losses, which are subtracted from the current CF CF_jet_divergence = F_divergence / ( p_chamber * A_throat ) # [-] Thrust coefficient after taking into account discharge factor and divergence loss CF_real_final = CF_jet_divergence - CF_viscous_loss # [-] The final thrust coefficient, also taking into account viscous loss F_real = CF_real_final * p_chamber * A_throat # [N] Real thrust, after taking into account of all the three proposed correction factors # Report "real" results print("\n === CORRECTED PERFORMANCE PARAMETERS === ") print(" Real mass flow: {:3.4f} mg/s".format(m_dot_real * 1e6)) print(" CF with divergence loss {:1.5f}".format(CF_jet_divergence)) print(" Real CF: {:1.5f}".format(CF_real_final)) print(" Real thrust: {:2.4f} mN".format(F_real * 1e3)) print(" Real Isp: {:3.2f}".format(F_real / m_dot_real / 9.80655)) return { 'm_dot_real': m_dot_real, 'm_dot_ideal': ep['m_dot'], 'F_real': CF_real_final }
import thermo.convection as conv import basic.chamber # Object to retrievere propellant properties from fp = FluidProperties("water") # Reference state for calculations p = 5e5 # [Pa] T_liquid = 300 # [K] T_gas = 500 # [K] m_dot = 1e-6 # [kg/s] For Kandlikar relation. #h_c = 100e-6 # [m] Channel depth/height # Density is needed to reverse calculate the reference length for Re, so it can be applied in other flow similarity parameters rho_liquid = fp.get_density(T=T_liquid, p=p) mu_liquid = fp.get_viscosity(T=T_liquid, p=p) # Range of Reynolds numbers to consider Reynolds = np.logspace(start=0, stop=2.8) D_hydraulic = np.logspace(start=-4.93, stop=-2.93) A_channel = D_hydraulic**2 Reynolds_2 = fp.get_Reynolds_from_mass_flow(T=T_liquid, p=p, L_ref=D_hydraulic, m_dot=m_dot, A=A_channel) # Range of hydraulic diameter that fit this Reynolds number #w_channel = 2*m_dot/( Reynolds_100 * mu_liquid ) - h_c # [m] #A_channel = h_c * w_channel # [m^2] #D_hydraulic = basic.chamber.hydraulic_diameter_rectangular(w_channel=w_channel,h_channel=h_c) # [m]
m_dot[it_AR.index][it_T.index] = ep['m_dot'] # [kg/s] Mass flow A_throat[it_AR.index][it_T.index] = ep['A_throat'] # [m^2] Throat area Isp[it_AR.index][it_T.index] = ep['Isp'] # [s] Specific impulse h_chamber[it_AR.index][it_T.index] = fp.get_enthalpy( T=float(T), p=p_chamber) # [J/kg] Enthalpy at chamber inlet # Throat stuff T_throat[it_AR.index][it_T.index] = ep[ 'T_throat'] # [K] Throat temperature p_throat[it_AR.index][it_T.index] = ep[ 'p_throat'] # [Pa] Throat pressure u_throat[it_AR.index][it_T.index] = ep[ 'u_throat'] # [K] Throat velocity Pr_throat[it_AR.index][it_T.index] = fp.get_Prandtl( T=ep['T_throat'], p=ep['p_throat']) # [-] Prandtl number in throat mu_throat[it_AR.index][it_T.index] = fp.get_viscosity( T=ep['T_throat'], p=ep['p_throat']) # [Pa*s] Dynamic viscosity in the throat # Nozzle exit stuff T_exit[it_AR.index][it_T.index] = ep[ 'T_exit'] # [K] Nozzle exit temperature p_exit[it_AR.index][it_T.index] = ep[ 'p_exit'] # [Pa] Nozzle exit pressure # Calculate ideal power h_inlet = fp.get_enthalpy(T=T_inlet, p=p_chamber) # [J/kg] Enthalpy at chamber inlet delta_h = h_chamber - h_inlet # [J/kg] Enthalpy change between inlet and chamber Pt_ideal = m_dot * delta_h # [W] Total ideal power consumption (no heat losses) ## Plot the results