def test_table(self): # Test if the table of the original paper can be reproduced Span2000 # Page 1410 of Span2000 fp = FluidProperties("nitrogen") pressure = 1e5 # 1 bar of pressure # T in, density out # Density taken in kg/mol from table, divided by molar mass in_out = ((300, 1.12328452104, 4), (330, 1.0209512786000001, 4), (450, 0.74846415864, 4), (600, 0.5613060987599999, 4), (1000, 0.33680607004, 4)) for T, rho, places in in_out: res_rho = fp.get_density(T=T, p=pressure) self.assertAlmostEqual(res_rho, rho, places=places) # Same again but for different pressure pressure = 1e6 # 10 bars of pressure # T in, density out # Density taken in kg/mol from table, divided by molar mass in_out = ((300, 11.248812894, 4), (330, 10.2058710336, 3), (450, 7.459989724000001, 3), (600, 5.591490608, 3), (1000, 3.3576957128, 3)) for T, rho, places in in_out: res_rho = fp.get_density(T=T, p=pressure) self.assertAlmostEqual(res_rho, rho, places=places)
def test_table(self): # Page 1410 of Span2000 fp = FluidProperties("nitrogen") pressure = 1e5 # 1 bar of pressure molar_mass = 0.02801348 # [kg/mol] also from Span2000 # T in, enthalpy out # Enthalpy taken in J/mol from table in_out = ((300, 8717.7, 0), (330, 9593.0, -1), (450, 13105, -1), (600, 17564, -1), (1000, 30135, -2)) for T, h, places in in_out: res_h = fp.get_enthalpy(T=T, p=pressure) self.assertAlmostEqual( res_h, h / molar_mass, places=places ) # Must be divided by molar mass as CoolProp gives enthalpy for J/kg # Testing again, but for another pressure pressure = 1e6 in_out = ((300, 8662.5, -1), (320, 9253.6, -1), (400, 11612, -1), (550, 16060, -2), (800, 23728, -2), (1000, 30152, -2)) for T, h, places in in_out: res_h = fp.get_enthalpy(T=T, p=pressure) self.assertAlmostEqual( res_h, h / molar_mass, places=places ) # Must be divided by molar mass as CoolProp gives enthalpy for J/kg
def test_table(self): # Page 1410 of Span2000 and further. Values must be converted to kg/m^3 from mol/dm^3 with molar mass = 0.02801348e3 fp = FluidProperties("nitrogen") # Just use a single temperature T = 300 # [K] # density in, pressure out in_out = ((1.1232845210400002, 1e5, -1), (2.2469612308, 2e5, -1), (5.6200643576000004, 5e5, -2), (11.248812894, 10e5, -1), (16.883724396, 15e5, -2), (22.523398189599998, 20e5, -1)) for rho, p, places in in_out: res_p = fp.get_pressure(T=T, rho=rho) self.assertAlmostEqual(res_p, p, places=places) # Again, but different temperature T = 1000 # [K] in_out = ((0.33680607004, 1e5, 0), (0.67338803224, 2e5, -1), ( 1.68170523136, 5e5, -1, ), (3.35775173976, 10e5, -2), (5.027859390400001, 15e5, -2), (6.6921402372, 20e5, -2)) for rho, p, places in in_out: res_p = fp.get_pressure(T=T, rho=rho) self.assertAlmostEqual(res_p, p, places=places)
def test_table(self): # Test reported values of gamma by taking cp/cv from Span2000 (page 1410 and below) # Test if the table of the original paper can be reproduced Span2000 # Page 1410 of Span2000 fp = FluidProperties("nitrogen") pressure = 1e5 # 1 bar of pressure # T in, gamma out # gamma not explicitly in table, but cp/cv is put in here instead in_out = ((300, 1.402784445511282, 2), (330, 1.4021113243761996, 2), (450, 1.3956356736242885, 3), (600, 1.3821100917431193, 3), (1000, 1.3411234112341124, 3)) for T, gamma, places in in_out: res_gamma = fp.get_specific_heat_ratio(T=T, p=pressure) self.assertAlmostEqual(res_gamma, gamma, places=places) # Again but with different pressure pressure = 1e6 # 10 bar of pressure # T in, gamma out # gamma not explicitly in table, but cp/cv is put in here instead in_out = ((300, 1.41666666666666672, 2), (330, 1.4126376256582096, 2), (450, 1.400947867298578, 3), (600, 1.3846859238881248, 3), (1000, 1.3419434194341942, 3)) for T, gamma, places in in_out: res_gamma = fp.get_specific_heat_ratio(T=T, p=pressure) self.assertAlmostEqual(res_gamma, gamma, places=places)
def plotGammaCurve(): fp = FluidProperties("water") temperature = np.linspace(start=500, stop=1000, num=200) pressure = np.linspace(start=1.0e5, stop=5.0e5, num=3) gamma = np.zeros((pressure.size, temperature.size)) p_iter = np.nditer(pressure, flags=['c_index']) T_iter = np.nditer(temperature, flags=['c_index']) for p in p_iter: i = p_iter.index T_iter.reset() for T in T_iter: j = T_iter.index gamma[i, j] = fp.get_specific_heat_ratio(T=float(T), p=float(p)) plt.figure() p_iter.reset() for p in p_iter: i = p_iter.index plt.plot(temperature, gamma[i, :], label="{:1.0f} bar".format(p * 1e-5)) plt.xlabel("Temperature - $T$ [K]") plt.ylabel("Specific heat ratio - $\\gamma$ [-] ") plt.grid() plt.legend(title="Pressure") plt.title("Specific heat ratio of water") plt.tight_layout()
def test_table(self): # Page 1410 of Span2000 fp = FluidProperties("nitrogen") pressure = 1e5 # 1 bar of pressureM # T in, cpout in_out = ((300, 1041.2844102196514, 0), (330, 1041.6413812207552, 0), (450, 1049.4947432450376, 0), (600, 1075.1966553245081, 0), (1000, 1167.2951736092768, 0)) for T, cp, places in in_out: res_cp = fp.get_cp(T=T, p=pressure) self.assertAlmostEqual(res_cp, cp, places=places) # Testing again, but for another pressure pressure = 1e6 in_out = ((300, 1055.9202212649052, 0), (320, 1054.1353662593865, 0), (400, 1052.3505112538678, 0), (550, 1068.4142063035367, 0), (800, 1123.7447114746187, 0), (1000, 1168.3660866125879, 0)) for T, cp, places in in_out: res_cp = fp.get_cp(T=T, p=pressure) self.assertAlmostEqual( res_cp, cp, places=places ) # Must be divided by molar mass as CoolProp gives enthalpy for J/kg
def plotSaturationCurve(): fp = FluidProperties("water") p_sat = np.linspace(start=0.01e5, stop=10.0e5, num=100) # [P] Pressure T_sat = fp.get_saturation_temperature(p_sat) # [K] plt.figure() plt.plot(T_sat, p_sat * 1e-5) plt.xlabel("Saturation temperature - $T_{{sat}}$ [K]") plt.ylabel("Saturation pressure - $p_{{sat}}$ [bar]") plt.grid() plt.title("Saturation curve for water") plt.tight_layout()
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, thermal conductivity in W/(m*K) in_out = ((100, 25.0, 103.834e-3, 6), (200, 10.0, 36.0099e-3, 7), (300, 5.0, 32.7694e-3, 7)) molar_mass = 0.02801348 # [kg/mol] also from Span2000 for T, rho, kappa, places in in_out: p = fp.get_pressure(T=T, rho=rho * molar_mass * 1e3) res_kappa = fp.get_thermal_conductivity(T=T, p=p) self.assertAlmostEqual(res_kappa, kappa, places=places)
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 testIntegrationWithKandlikarRelation(self): # Same test but only focussing on Nusselt number for Kandlikar relation, as that would prove thar arguments are correctly passed to function td = thrusters.thruster_data.td_verification_one # Dummy dictionary with thruster data for verifcation Nu_func_gas = thermo.convection.Nu_DB Nu_func_liquid = thermo.convection.Nu_Kandlikar_NDB_Re_low_sat_gas_constant_wall_temp_square_water T_wall = td['T_wall'] w_channel = td['w_channel'] T_inlet = td['T_inlet'] T_chamber = td['T_chamber'] p_ref = td['p_inlet'] m_dot = td['m_dot'] h_channel = td['h_channel'] fp = FluidProperties(td['propellant']) # Check just the liquid/multi-phase Nusselt number of the Kandlikar relation, to ensure integration is correct exp_Nu_liquid_multi = 46.74856022286813 res = zD.two_phase_single_channel( T_wall=T_wall, w_channel=w_channel, Nu_func_gas=Nu_func_gas, Nu_func_liquid=Nu_func_liquid, T_inlet=T_inlet, T_chamber=T_chamber, p_ref=p_ref, m_dot=m_dot, h_channel=h_channel, fp=fp, print_info=False) self.assertAlmostEqual(exp_Nu_liquid_multi, res['Nu_liquid_multi'], delta=0.01*exp_Nu_liquid_multi)
def engine_performance_from_F_and_T(F_desired, p_chamber, T_chamber, AR_exit, p_back, fp: FluidProperties): """ Returns Nozzle Inlet State based on IRT Args: F_desired (N): Desired thrust p_chamber (Pa): Chamber thrust/nozzle inlet pressure T_chamber (K): Chamber temperature AR_exit (-): A_exit / A_throat p_back (Pa): Back pressure fp (FluidProperties): Used to access fluid properties Raises: Exception: Raised when no solution is found for given input Returns: dict{A_throat [m^2], m_dot [kg/s]}: Throat area and mass flow """ # Default range for temperature for root-finding algorithm A_low = 1e-22 # [K] A_high = 1e-3 # [K] R = fp.get_specific_gas_constant() # [J/(kg*K)] gamma = fp.get_specific_heat_ratio(T_chamber, p_chamber) # [-] Specific heat ratio # If x is zero, then the desired thrust is found. Gamma is changed depending on temperature as well x = lambda A_t: F_desired - get_engine_performance(p_chamber=p_chamber, T_chamber=T_chamber, A_throat=A_t, \ AR_exit=AR_exit,p_back=p_back, gamma=gamma,R=R)['thrust'] root_result = optimize.root_scalar(x, bracket=[A_low, A_high], xtol=1e-12) if root_result.converged: A_throat = root_result.root # [m^2] else: raise Exception("No solution found for given temperature") # Now the chamber temperature is known, return the unknown parameters ep = get_engine_performance(p_chamber=p_chamber, T_chamber=T_chamber, A_throat=A_throat, \ AR_exit=AR_exit,p_back=p_back, gamma=fp.get_specific_heat_ratio(T_chamber,p_chamber),R=R) # Add throat area to dictionary ep['A_throat'] = A_throat # This is usually an input for get_engine_performance, but now added to make it one complete solution #ep['Isp'] = IRT.effective_ISP(thrust=F_desired,m_dot=ep['m_dot']) # [s] Effective specific impulse return ep
def test_one(self): # Simply re-using case above, only heating fp = FluidProperties("HEOS::Water") T_inlet = 700 # [K] T_outlet = 900 # [K] # This makes the bulk temperature 800 K p = 5e5 # [Pa] D_h = 1e-3 u = 1e-3 T_bulk = (T_outlet + T_inlet) / 2 Re = fp.get_Reynolds_from_velocity(T=T_bulk, p=p, L_ref=D_h, u=u) Pr = fp.get_Prandtl(T=T_bulk, p=p) arguments = {'fp': fp, 'Re': Re, 'Pr': Pr} exp_Nu = 0.0018785745665208552 res_Nu = thermo.convection.Nu_DB(args=arguments) self.assertAlmostEqual(exp_Nu, res_Nu, delta=0.00001 * exp_Nu / res_Nu)
def h_conv_from_Stanton(Stanton, u, T_ref, p_ref, fp: FluidProperties): """Return heat transfer coefficient dependent on Stanton number and thermodynamic state\ Temperature and pressure are passed instead of T and p, as these abstract away constant computations of cp and rho\ and it is easier to pass around the same state variables time and time again WARNING: REFERENCE THERMODYNAMIC STATE (T_ref, p_ref) MUST BE EQUAL TO THOSE WITH WHICH NUSSELT NUMBER WAS DETERMINED Args: Stanton (-): Stanton number: dimensionless flow characteristic u (m/s): flow velocity T_ref (K): Temperature p_ref (Pa): Pressure fp (FluidProperties): object to use to obtain properties of fluid Returns: h_conv (W/(m^2*K)): convective heat transfer coefficient based on Stanton number, flow velocity and thermodynamic state """ cp = fp.get_cp(T=T_ref, p=p_ref) # [J/kg] Specific heat capacity under constant pressure rho = fp.get_density(T=T_ref,p=p_ref) # [kg/m^3] Fluid density return Stanton*rho*u*cp # [W/(m^2*K)] h_conv: Convective heat transfer coefficient
def setUp(self): # Inputs fp = FluidProperties('water') p_inlet = 2e5 # [Pa] Inlet pressure steps = 10 # [-] Number of data point m_dot = 1e-4 # [kg/s] self.prep = oneD.prepare_homogenous_transition(steps=steps, p=p_inlet, m_dot=m_dot, fp=fp) return super().setUp()
def test_webbook_data(self): # Pick some random points of NIST webbook to calculate Prandlt numbers, instead of re-using tables of different functions # Source: https://webbook.nist.gov/cgi/fluid.cgi?T=300&PLow=1&PHigh=10&PInc=1&Applet=on&Digits=5&ID=C7727379&Action=Load&Type=IsoTherm&TUnit=K&PUnit=bar&DUnit=kg%2Fm3&HUnit=kJ%2Fkg&WUnit=m%2Fs&VisUnit=Pa*s&STUnit=N%2Fm&RefState=DEF fp = FluidProperties("nitrogen") T = 300 # [K] p = 2e5 # [Pa] exp_Pr = 0.721667851210939 res_Pr = fp.get_Prandtl(T=T, p=p) self.assertAlmostEqual(exp_Pr, res_Pr, places=2) p = 10e5 exp_Pr = 0.7279411479231152 res_Pr = fp.get_Prandtl(T=T, p=p) self.assertAlmostEqual(exp_Pr, res_Pr, places=2) # Source for 1000K : https://webbook.nist.gov/cgi/fluid.cgi?Action=Load&ID=C7727379&Type=IsoTherm&Digits=5&PLow=1&PHigh=10&PInc=1&T=1000&RefState=DEF&TUnit=K&PUnit=bar&DUnit=kg%2Fm3&HUnit=kJ%2Fkg&WUnit=m%2Fs&VisUnit=Pa*s&STUnit=N%2Fm T = 1000 # [K] p = 2e5 # [Pa] exp_Pr = 0.7359141666666666 res_Pr = fp.get_Prandtl(T=T, p=p) self.assertAlmostEqual(exp_Pr, res_Pr, places=1) T = 1000 # [K] p = 10e5 # [Pa] exp_Pr = 0.7361587678944341 res_Pr = fp.get_Prandtl(T=T, p=p) self.assertAlmostEqual(exp_Pr, res_Pr, places=1)
def test_webbook_data(self): # Pick some random points of NIST webbook to calculate Reynolds numbers, instead of re-using tables of different functions # Source: https://webbook.nist.gov/cgi/fluid.cgi?T=300&PLow=1&PHigh=10&PInc=1&Applet=on&Digits=5&ID=C7727379&Action=Load&Type=IsoTherm&TUnit=K&PUnit=bar&DUnit=kg%2Fm3&HUnit=kJ%2Fkg&WUnit=m%2Fs&VisUnit=Pa*s&STUnit=N%2Fm&RefState=DEF fp = FluidProperties("nitrogen") T = 300 # [K] p = 1e5 # [Pa] u = 1 # [m/s] L = 1 # [m] exp_Re = 62764.70916913448 res_Re = fp.get_Reynolds_from_velocity(T=T, p=p, u=u, L_ref=L) self.assertAlmostEqual(exp_Re, res_Re, places=-2) u = u * 2 # [m/s] exp_Re = 2 * 62764.70916913448 res_Re = fp.get_Reynolds_from_velocity(T=T, p=p, u=u, L_ref=L) self.assertAlmostEqual(exp_Re, res_Re, places=-2) L = 1 / 3 # [m] exp_Re = 2 / 3 * 62764.70916913448 res_Re = fp.get_Reynolds_from_velocity(T=T, p=p, u=u, L_ref=L) self.assertAlmostEqual(exp_Re, res_Re, places=-2) T = 1000 # [K] p = 10e5 # [Pa] u = 1e-3 # [m/s] L = 10e-3 # [m] exp_Re = 0.806359422656644 res_Re = fp.get_Reynolds_from_velocity(T=T, p=p, u=u, L_ref=L) self.assertAlmostEqual(exp_Re, res_Re, places=2)
def setUp(self): # Inputs fp = FluidProperties('water') T_outlet = 450 # [K] p_inlet = 2e5 # [Pa] Inlet pressure steps = 10 # [-] Number of data point m_dot = 1e-3 # [kg/s] self.prep = oneD.prepare_single_phase_gas(T_outlet=T_outlet, steps=steps, p_ref=p_inlet, m_dot=m_dot, fp=fp) return super().setUp()
def ideal_enthalpy_change(T_inlet, p_inlet, T_outlet, p_outlet, fp: FluidProperties): """Returns specific enthalpy change based on simple chamber inlet and outlet conditions. This should give the power the micro-heater must transfer in ideal conditions with no heat losses. In addition returns a warning if the final state is not gaseous. Arguments: T_inlet {K} -- Inlet temperature p_inlet {Pa} -- Inlet pressure T_outlet {K} -- Outlet temperature p_outlet {Pa} -- Outlet pressure fp {object} -- FluidProperties object Returns: delta_h {J/(kg*K)} -- """ h_inlet = fp.get_enthalpy(T=T_inlet, p=p_inlet) h_outlet = fp.get_enthalpy(T=T_outlet, p=p_outlet) outlet_phase = fp.get_phase(T=T_outlet,p=p_outlet) if not (outlet_phase == 'gas' or outlet_phase == 'supercritical_gas'): print("Warning: Phase at chamber exit is not gaseous but {}".format(outlet_phase)) return h_outlet-h_inlet
def run2(): #Calcuate heating efficiency of heaters in literature m_dot = 0.83e-6 # [kg/s] mass flow T_in = 24+273.15 # [K] Inlet temperature T_out = 426.65 # [K] Outlet temperature p = 5.15e5 # [Pa] Pressure P_total = 8.19 # [W] Total electrical power fp = FluidProperties("water") # Specific enthalpy at inlet and outlet h_in = fp.get_enthalpy(T=T_in, p=p) # [J/kg] Inlet h_out = fp.get_enthalpy(T=T_out, p=p) # [J/kg] Outlet P_delta_h = m_dot*(h_out-h_in) # [W] Power raising enthalpy efficiency = P_delta_h/P_total # [-] print("Exit phase: {}".format(fp.get_phase(T=T_out,p=p))) print("P_delta_h: {:1.2f} W".format(P_delta_h)) print("Micro-heater efficiency: {:1.2f} ".format(efficiency)) print("T_in = {:3.0f} K \t\t T_out = {:3.0f} K".format(T_in,T_out)) print("Mass flow = {:2.2f} mg/s".format(m_dot*1e6))
def test_water_input(self): # Source: https://webbook.nist.gov/cgi/fluid.cgi?Action=Load&ID=C7732185&Type=IsoBar&Digits=5&P=0.1&THigh=1000&TLow=300&TInc=100&RefState=DEF&TUnit=K&PUnit=MPa&DUnit=kg%2Fm3&HUnit=kJ%2Fkg&WUnit=m%2Fs&VisUnit=Pa*s&STUnit=N%2Fm fp = FluidProperties("HEOS::Water") T = 500 # [K] p = 5e5 # [Pa] u = 1e-3 # [m/s] St = 1 exp_h_conv = 4.6448084 # [W/(m^2*K)] Convective heat transfer res_h_conv = chamber.h_conv_from_Stanton(Stanton=St, T_ref=T, p_ref=p, u=u, fp=fp) self.assertAlmostEqual(exp_h_conv, res_h_conv, delta=exp_h_conv / res_h_conv * 0.0001)
def test_one(self): # Calculate a known case manually, and check for similarity # Source: https://webbook.nist.gov/cgi/fluid.cgi?Action=Load&ID=C7732185&Type=IsoBar&Digits=5&P=0.5&THigh=1000&TLow=300&TInc=100&RefState=DEF&TUnit=K&PUnit=MPa&DUnit=kg%2Fm3&HUnit=kJ%2Fkg&WUnit=m%2Fs&VisUnit=Pa*s&STUnit=N%2Fm fp = FluidProperties("HEOS::Water") T_inlet = 700 # [K] T_outlet = 900 # [K] # This makes the bulk temperature 800 K p = 5e5 # [Pa] D_h = 1e-3 L = 1000e-3 m_dot = 1e-6 # [kg/s] A = 0.0007359976448075365 # [m^2] exp_Nu = 0.0018785745665208552 res_Nu = thermo.convection.Nusselt_Dittus_Boelter( T_inlet=T_inlet, T_outlet=T_outlet, p=p, D_hydraulic=D_h, L_channel=L, m_dot=m_dot, A=A, fp=fp, supressExceptions=True) self.assertAlmostEqual(exp_Nu, res_Nu, delta=0.01 * exp_Nu) # Same but with cooling assumption exp_Nu = 0.001896461381677763 res_Nu = thermo.convection.Nusselt_Dittus_Boelter( T_inlet=T_inlet, T_outlet=T_outlet, p=p, D_hydraulic=D_h, L_channel=L, m_dot=m_dot, A=A, fp=fp, heating=False, supressExceptions=True) self.assertAlmostEqual(exp_Nu, res_Nu, delta=0.01 * exp_Nu)
def test_one(self): #Simply Re-using case above again fp = FluidProperties("water") T_inlet = 700 # [K] T_outlet = 900 # [K] # This makes the bulk temperature 800 K p = 5e5 # [Pa] D_h = 1e-3 m_dot = 1e-6 # [kg/s] A = 0.0007359976448075365 # [m^2] Nu_func = Nu_DB T_bulk = (T_inlet + T_outlet) / 2 Pr = 0.9095872167254094 Re = 0.04578292954139569 exp_St = 0.0018785745665208552 / (Re * Pr) res_St = thermo.convection.Stanton_from_Nusselt_func_and_velocity( Nu_func=Nu_func, m_dot=m_dot, A=A, T_ref=T_bulk, p_ref=p, L_ref=D_h, fp=fp) self.assertAlmostEqual(exp_St, res_St, delta=exp_St * 0.01) def dummy_func(args): return 5 # Another test with a dummy func exp_St = 5 / (Re * Pr) res_St = thermo.convection.Stanton_from_Nusselt_func_and_velocity( Nu_func=dummy_func, m_dot=m_dot, A=A, T_ref=T_bulk, p_ref=p, L_ref=D_h, fp=fp) self.assertAlmostEqual(exp_St, res_St, delta=exp_St * 0.01)
def setUp(self): # Inputs fp = FluidProperties('water') T_inlet = 300 # [K] p_inlet = 2e5 # [Pa] Inlet pressure steps = 3 # [-] 3 steps for convenience m_dot = 0.1e-3 # [kg/s] # Get the prepared thermodynamic values self.prep = oneD.prepare_single_phase_liquid(T_inlet=T_inlet, steps=steps, p_ref=p_inlet, m_dot=m_dot, fp=fp) # set the Nusselt function Nu_func_liquid = thermo.convection.Nu_DB # [-] Nusselt number for liquid phase # Set remaining inputs T_wall = 500 # [K] Wall temperature\ #Geometry w_h = 100e-6 # [m] Width and height A_channel = w_h**2 # [m^2] wetted_perimeter = 4 * w_h # [m] D_hydr = 4 * A_channel / wetted_perimeter # [m] self.res = oneD.calc_channel_single_phase(\ T = self.prep['T'], Q_dot= self.prep['Q_dot'], rho = self.prep['rho'], Pr = self.prep['Pr'], kappa = self.prep['kappa'], mu = self.prep['mu'], p_ref=p_inlet, m_dot=m_dot, T_wall=T_wall, D_hydr=D_hydr, wetted_perimeter=wetted_perimeter, A_channel=A_channel, Nu_func=Nu_func_liquid, fp=fp ) return super().setUp()
def calc_P_delta_h(T_in,T_out,m_dot,p_chamber): """Calculate power required to raise temperature of flow with mass flow m_dot Arguments: T_in {K} -- Inlet temperature T_out {K} -- Outlet temperature m_dot {kg/s} -- Mass flow p_chamber {Pa} -- Chamber pressure Returns: {W} - Required power """ # Check if outlet state is gaseous fp = FluidProperties("water") outlet_phase = fp.get_phase(T=T_out,p=p_chamber) if not (outlet_phase == "gas" or outlet_phase == "supercritical_gas"): print(fp.get_phase(T=T_out,p=p_chamber)) raise ValueError("Assumed outlet temperature is not not in gas phase") Delta_h = fp.get_enthalpy(T=T_out, p=p_chamber) - fp.get_enthalpy(T=T_in, p=p_chamber) return m_dot*Delta_h
def two_phase_single_channel(T_wall, w_channel, Nu_func_gas, Nu_func_liquid, T_inlet, T_chamber, p_ref, m_dot, h_channel, fp: FluidProperties, print_info=True): """ Function that calculates the total power consumption of a specific chamber, in order to optimize the chamber Args: T_wall (K): Wall temperature w_channel (m): Channel width Nu_func_gas (-): Nusselt function for gas phase Nu_func_liquid (-) Nusselt function for liquid phase T_inlet (K): Chamber inlet temperature T_chamber (K): Chamber outlet temperature (same as T_c in IRT) p_ref (Pa): Reference pressure for the Nusselt relation and flow similary parameters (same as inlet pressure as no pressure drop is assumed) m_dot (kg/s): Mass flow h_channel (m): Channel height w_channel_margin (m): The amount of margin around the chamber for structural reasons. Important because it also radiates heat fp (- ): [description] print_info(Bool): for debugging purposes """ # Calculate saturation temperature, to determine where transition from gas to liquid occurs T_sat = fp.get_saturation_temperature(p=p_ref) # [K] # Sanity check on input assert (T_chamber > T_sat) assert (T_wall > T_chamber) # Calculate the two reference temperatures for the separated phases T_bulk_gas = (T_sat + T_chamber) / 2 # [K] Bulk temperature gas phase T_bulk_liquid_multi = ( T_inlet + T_sat) / 2 # [K] Bulk temperature of liquid and multi-phase flow # Calculate the density at these reference points rho_bulk_gas = fp.get_density(T=T_bulk_gas, p=p_ref) # [kg/m^3] rho_bulk_liquid_multi = fp.get_density(T=T_bulk_liquid_multi, p=p_ref) # [kg/m^3] # Channel geometry A_channel = w_channel * h_channel # [m^2] Area through which the fluid flows wetted_perimeter = wetted_perimeter_rectangular( w_channel=w_channel, h_channel=h_channel ) # [m] Distance of channel cross-section in contact with fluid D_hydraulic = hydraulic_diameter_rectangular( w_channel=w_channel, h_channel=h_channel) # [m] Hydraulic diameter # Flow similarity parameters (for debugging and Nu calculatoin purposes) Re_bulk_gas = fp.get_Reynolds_from_mass_flow( m_dot=m_dot, p=p_ref, T=T_bulk_gas, L_ref=D_hydraulic, A=A_channel) # [-] Bulk Reynolds number in the gas phase Re_bulk_liquid_multi = fp.get_Reynolds_from_mass_flow( m_dot=m_dot, p=p_ref, T=T_bulk_liquid_multi, L_ref=D_hydraulic, A=A_channel) # [-] Bulk Reynolds number in the liquid/multi-phase Pr_bulk_gas = fp.get_Prandtl( T=T_bulk_gas, p=p_ref) # [-] Prandtl number in the gas phase Pr_bulk_liquid_multi = fp.get_Prandtl( T=T_bulk_liquid_multi, p=p_ref) # [-] Prandtl number in liquid/multi-phase Bo_sat = fp.get_Bond_number( p_sat=p_ref, L_ref=D_hydraulic ) # [-] Bond number at saturation pressure (assumed to be p_ref) # Calculate Nusselt number in both sections args_gas = { 'Re': Re_bulk_gas, # Arguments for Nusselt function (gas phase) 'Pr': Pr_bulk_gas, 'Bo': Bo_sat, } args_liquid_multi = { # Arguments for Nusselt function (liquid/multi phase) 'Re': Re_bulk_liquid_multi, 'Pr': Pr_bulk_liquid_multi, 'Bo': Bo_sat, } Nu_gas = Nu_func_gas(args=args_gas) Nu_liquid_multi = Nu_func_liquid(args=args_liquid_multi) # Calculate Stanton number in both sections St_gas = Stanton_from_Nusselt_and_velocity( Nu=Nu_gas, T_ref=T_bulk_gas, p_ref=p_ref, L_ref=D_hydraulic, m_dot=m_dot, A=A_channel, fp=fp) # [-] Stanton number in gas phase St_liquid_multi = Stanton_from_Nusselt_and_velocity( Nu_liquid_multi, T_ref=T_bulk_liquid_multi, p_ref=p_ref, L_ref=D_hydraulic, m_dot=m_dot, A=A_channel, fp=fp) # [-] Stanton number in liquid phase # Calculate velocity for convection parameter (bulk temp used as reference for phase) u_bulk_gas = velocity_from_mass_flow( A=A_channel, m_dot=m_dot, rho=rho_bulk_gas) # [m/s] Velocity at the gas bulk reference state u_bulk_liquid_multi = velocity_from_mass_flow( A=A_channel, m_dot=m_dot, rho=rho_bulk_liquid_multi ) # [m/s] Velocity at the liquid/multi-phase bulk reference state # Convective parameter h_conv_gas = h_conv_from_Stanton( Stanton=St_gas, u=u_bulk_gas, T_ref=T_bulk_gas, p_ref=p_ref, fp=fp ) # [W/(m^2*K)] Convective heat transfer coefficient at bulk gas state h_conv_liquid_multi = h_conv_from_Stanton( Stanton=St_liquid_multi, u=u_bulk_liquid_multi, T_ref=T_bulk_liquid_multi, p_ref=p_ref, fp=fp ) # [W/(m^2*K)] Convective heat transfer coefficient at bulk liquid/multi-phase state # Required specific enthalpy change for heating the separate sections h_outlet = fp.get_enthalpy( T=T_chamber, p=p_ref) # [J/kg] Specific enthalpy at the outlet h_sat_gas = fp.get_saturation_enthalpy_gas( p=p_ref) # [J/kg] Specific enthalpy of saturated gas h_inlet = fp.get_enthalpy(T=T_inlet, p=p_ref) # [J/kg] # Required specific enthalpy increases delta_h_gas = h_outlet - h_sat_gas # [J/kg] Enthalpy increase needed to go from saturated gas to outlet enthalpy delta_h_liquid_multi = h_sat_gas - h_inlet # [J/k] Enthalpy increase needed to go from inlet enthalpy to saturated gas # Required power for those enthalpy changes at the given mass flow Q_dot_gas = required_power(m_dot=m_dot, delta_h=delta_h_gas) # [W] Q_dot_liquid_multi = required_power(m_dot=m_dot, delta_h=delta_h_liquid_multi) # [W] # Required heater area to achieve the required power A_heater_gas = required_heater_area(Q_dot=Q_dot_gas, h_conv=h_conv_gas, T_wall=T_wall, T_ref=T_bulk_gas) # [m^2] A_heater_liquid_multi = required_heater_area( Q_dot=Q_dot_liquid_multi, h_conv=h_conv_liquid_multi, T_wall=T_wall, T_ref=T_bulk_liquid_multi) # [m^2] # Required length to achieve desired area L_channel_gas = A_heater_gas / wetted_perimeter # [m] Length of channel after gas is saturated L_channel_liquid_multi = A_heater_liquid_multi / wetted_perimeter # [m] Length of channel after heater L_channel = L_channel_gas + L_channel_liquid_multi # [m] L_hydrodynamic_entrance = D_hydraulic * Re_bulk_liquid_multi * 0.04 # [m] Hydrodynamic entrance to estimate if the flow is fully developed assert (h_outlet > h_sat_gas) assert (h_sat_gas > h_inlet) if (print_info): print("\n--- SPECIFIC ENTHALPY AT DIFFERENT STATIONS ---") print("h_outlet: {:4.3f} J/kg".format(h_outlet)) print("h_sat_gas: {:4.3f} J/kg".format(h_sat_gas)) print("h_inlet: {:4.3f} J/kg".format(h_inlet)) print("\n --- REQUIRED POWER ---") print("Q_dot_gas: {:2.5f} W".format(Q_dot_gas)) print("Q_dot_liquid_multi: {:2.5f} W".format(Q_dot_liquid_multi)) print("\n --- BULK GAS PHASE PARAMETERS --- ") print("u: {:3.2f} m/s".format(u_bulk_gas)) print("Nu: {}".format(Nu_gas)) print("Re: {}".format(Re_bulk_gas)) print("Pr: {}".format(Pr_bulk_gas)) print("St: {}".format(St_gas)) print("Bo_sat: {}".format(Bo_sat)) print("\n --- BULK LIQUID/MULTI-PHASE PARAMETERS ---") print("u: {:3.4f} m/s".format(u_bulk_liquid_multi)) print("Nu: {}".format(Nu_liquid_multi)) print("Re: {}".format(Re_bulk_liquid_multi)) print("Pr: {}".format(Pr_bulk_liquid_multi)) print("St: {}".format(St_liquid_multi)) print("\n --- CHARACTERISTIC GEOMETRIC VALUES --- ") print("Hydrodynamic entance length: {:3.3f} micron".format( L_hydrodynamic_entrance * 1e6)) print("Hydraulic diameter: {:3.3f} micron".format(D_hydraulic * 1e6)) print("L/D: {:4.2f} ".format(L_channel / D_hydraulic)) print("L/X_T {:4.2f}".format(L_channel / L_hydrodynamic_entrance)) print("\n --- RESULTING GEOMETRY ---") print("Total length: {:3.3f} mm".format(L_channel * 1e3)) print("Length (liquid/multi): {:3.3f} mm".format( L_channel_liquid_multi * 1e3)) print("Length (gas): {:3.4f} mm".format(L_channel_gas * 1e3)) print("Relative length (gas) {:3.3f} \%".format(L_channel_gas / L_channel * 100)) ## Return a dictionary with results and interesting intermediate values res = { "L_channel": L_channel, # [m] Total length of channel "D_hydraulic": D_hydraulic, # [m] Hydraulic diameter of channel "Nu_liquid_multi": Nu_liquid_multi, # [-] Nusselt number of liquid/multi-phase flow "Pr_bulk_liquid_multi": Pr_bulk_liquid_multi, # [-] Prandlt number of liquid/multi-phase flow "Re_bulk_liquid_multi": Re_bulk_liquid_multi, # [-] Reynolds number of liquid/multi-phase flow "St_liquid_multi": St_liquid_multi, # [-] Stanton number of liquid/multi-phase flow "h_conv_liquid_multi": h_conv_liquid_multi, # [W/(m^2*K)] Heat transfer coefficient "A_heater_liquid_multi": A_heater_liquid_multi, # [m^2] Required heater area for liquid/multi-phase flow "L_channel_liquid_multi": L_channel_liquid_multi, # [m] Length of channel to get required heater area "u_bulk_liquid_multi": u_bulk_liquid_multi, # [m/s] Bulk flow velocity of liquid/multi-phase flow "rho_bulk_liquid_multi": rho_bulk_liquid_multi, # [kg/m^3] Bulk density of liquid/multi-phase flow "T_bulk_liquid_multi": T_bulk_liquid_multi, # [K] Bulk temperature of liquid/multi-phase flow "delta_h_liquid_multi": delta_h_liquid_multi, # [J/kg] Enthalpy change from inlet to saturated gas "Q_dot_liquid_multi": Q_dot_liquid_multi, # [W] Power required for enthalpy change ## Same thing but for gas values "Nu_gas": Nu_gas, # [-] "Pr_bulk_gas": Pr_bulk_gas, # [-] "Re_bulk_gas": Re_bulk_gas, # [-] "St_gas": St_gas, # [-] "h_conv_gas": h_conv_gas, # [W/(m^2*K)] "A_heater_gas": A_heater_gas, # [m^2] "L_channel_gas": L_channel_gas, # [m] "u_bulk_gas": u_bulk_gas, # [m/s] "rho_bulk_gas": rho_bulk_gas, # [kg/m^3] "T_bulk_gas": T_bulk_gas, # [K] "delta_h_gas": delta_h_gas, # [J/kg] "Q_dot_gas": Q_dot_gas, # [W] } return res
def chamber_performance_from_Nu(Nu_func, T_inlet, T_chamber, T_ref, T_wall, p_ref, m_dot, A_channel, L_ref, fp: FluidProperties): """ Function that calculates the power consumption and heating area for a specific chamber Args: Nu_func (-): Nusselt function, that implement the emperical relation of choice T_inlet (K): Chamber inlet temperature T_chamber (K): Chamber outlet temperature (same as T_chamber for IRT) T_ref (K): Reference temperature for the Nusselt relation and flow similary parameters T_wall (K): Wall temperature p_ref (Pa): Chamber pressure (no pressure drop assumed) m_dot (kg/s): Mass flow A_channel (m^2): Cross-sectional area of the chamber, through which the fluid flows L_ref (m): Reference length for Nusselt relation and flow similarty parameters fp (FluidProperties): Object from which Fluid Properties are determined Returns: dictionary with heater area, power required to heat up flow and Nusselt number """ ## Make sure all parameters are calculated at the same reference state (including velocity!) # Pr and Re parameters needed for most Nusselt relation rho_ref = fp.get_density(T=T_ref, p=p_ref) # [kg/m^3] Reference density u_ref = velocity_from_mass_flow( A=A_channel, m_dot=m_dot, rho=rho_ref) # [m/s] Speed at reference state print("u_ref {} m/s".format(u_ref)) Re_ref = fp.get_Reynolds_from_mass_flow( T=T_ref, p=p_ref, L_ref=L_ref, m_dot=m_dot, A=A_channel) # [-] Reynolds number at reference state Pr_ref = fp.get_Prandtl(T=T_ref, p=p_ref) # [-] Prandtl number at reference state # Now the Nusselt can be determined Nusselt = Nu_func(args={ 'Re': Re_ref, 'Pr': Pr_ref }) # [-] Nusselt number at given state (used for plotting purposes) Stanton = Stanton_from_Nusselt_func_and_velocity( Nu_func=Nu_func, m_dot=m_dot, A=A_channel, T_ref=T_ref, p_ref=p_ref, L_ref=L_ref, fp=fp) # [-] Stanton number at reference state h_conv = h_conv_from_Stanton(Stanton=Stanton, u=u_ref, T_ref=T_ref, p_ref=p_ref, fp=fp) # Now determine how much energy must be convected delta_h = ideal_enthalpy_change(T_inlet=T_inlet, p_inlet=p_ref, T_outlet=T_chamber, p_outlet=p_ref, fp=fp) # [J/kg] Q_dot = required_power( m_dot=m_dot, delta_h=delta_h) # [W] Required power to achieve delta_h A_heater = required_heater_area(Q_dot=Q_dot, h_conv=h_conv, T_wall=T_wall, T_ref=T_ref) # [m^2] assert (A_heater > 0) # Return a dictionary with interesting values return { 'A_heater': A_heater, 'Q_dot': Q_dot, 'Nusselt': Nusselt, 'Re_ref': Re_ref, 'Pr_ref': Pr_ref }
import math from thermo.prop import FluidProperties import thermo.convection import thermo.two_phase as tp from thermo.prop import FluidProperties import models.one_D as oneD ##################################################################################### ### 1D RECTANGULAR MULTICHANNEL 4mN ## ##################################################################################### settings_1D_rectangular_multichannel = {} ## Fixed design parameters settings_1D_rectangular_multichannel['FDP'] = { 'fp': FluidProperties( 'water' ), # (object) Interface with Coolprop that returns all necessary properties of water 'p_inlet': 5e5, # [Pa] Pressure at inlet 'p_back': 0, # [Pa] NOTE: back pressure MUST be vacuum/zero for nozzle adjustment to work correctly 'T_inlet': 300, # [K] Heating chamber inlet temperature 'h_channel': 100e-6, # [m] Channel depth/height 'AR_exit': 10, # [-] Exit area ratio of nozzles 'inlet_manifold_length_factor': 2, # [m] Multiplication factor with inlet manifold width to determine manifold length 'inlet_manifold_width_factor': 5.5, # [-] Multiplication factor (with channel width to determine margin in chamber) 'l_exit_manifold': 0, # [m] Length between the end of multiple channels and start of convergent nozzle 'convergent_half_angle': math.radians(45), # [rad] Half-angle of the convergent part of the nozzle
## File to show Nusselt number predicted by Kandlikar import numpy as np import matplotlib.pyplot as plt from thermo.two_phase import Nu_Kandlikar_NBD_dryout, Nu_Kandlikar_NBD_CBD_dryout from thermo.prop import FluidProperties from thermo.convection import Nu_laminar_developed_constant_wall_temp_square, Nu_DB from basic.chamber import Reynolds_from_mass_flow fp = FluidProperties("water") Nu_func_le_laminar = Nu_laminar_developed_constant_wall_temp_square Nu_func_le_turbulent = Nu_DB Nu_func_tp = Nu_Kandlikar_NBD_CBD_dryout # Design parameters p = 5e5 # Pressure through channel [Pa] m_dot = 200e-6 # [kg/s] A_channel = 1e-6 # [m^2] Channel cross sections D_h = 1e-6 * np.array((100, 200, 500)) # [m] Hydraulic diameters # Mass flux calculated for reference G = m_dot / A_channel # [kg/(m^2*s)] Mass flux print("Mass flux: {:3.1f} kg/(m^2*s)".format(G)) # Saturatoin conditions rho_l = fp.get_liquid_density_at_psat( p_sat=p) # [kg/m^3] Liquid density at saturation point rho_g = fp.get_vapour_density_at_psat( p_sat=p) # [kg/m^3] Vapour density at saturation point
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 }
def printBasicStuff(): fp = FluidProperties("water") print("R: {:3.2f} J/(kg*K)".format(fp.get_specific_gas_constant()))