def setUp(self): # Results based on manual calculation in excelsheet Verification_one_d.xlsx fp = FluidProperties('water') p_inlet = 2e5 # [Pa] Inlet pressure steps = 3 # [-] Number of data point m_dot = 0.1e-3 # [kg/s] T_wall = 500 # [K] T_inlet = 300 # [K] T_outlet = 450 channel_amount = 1 # Nusselt functions Nusselt_relations = { 'Nu_func_gas': thermo.convection. Nu_DB, # [-] Function to calculate Nusselt number (gas phase) 'Nu_func_liquid': thermo.convection. Nu_DB, # [-] Function to caculate Nusselt number (liquid phase) 'Nu_func_two_phase': tp. Nu_Kandlikar_NBD_dryout, # [-] Function to calculate Nusselt number (two-phase) 'Nu_func_le': thermo.convection. Nu_DB, # [-] Function to calculate Nusselt in two-phase, AS IF the flow was entirely liquid (two-phase, le) 'Nu_func_dryout': tp. Nu_DB_two_phase, #thermo.two_phase.Nu_DB_two_phase # [-] Function to calculate the Nusselt number after dry-out. It is up to Nu_func_two_phase to decide if/how to apply it' } # Pressure drop functions. Testing 3 seperate functions to check integraotin, modularity, as well as separate functions pressure_drop_relations = { 'l': oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds, 'tp': oneD.calc_two_phase_frictional_pressure_drop_low_Reynolds, 'g': oneD.calc_single_phase_frictional_pressure_drop_high_Reynolds, 'contraction': oneD.calc_single_phase_contraction_pressure_drop_Kawahara2015, } #Geometry w_channel = 100e-6 # [m] Width and height h_channel = 100e-6 # [m] Channel height area_ratio_contraction = 0 # [-] Arbitrary input for contraction pressure drop steps = 3 self.prepared_values = oneD.full_homogenous_preparation(\ T_inlet=T_inlet, T_outlet=T_outlet, m_dot=m_dot, p_ref=p_inlet, steps_l=steps, steps_tp=steps, steps_g=steps, fp=fp) self.res = oneD.rectangular_multi_channel_homogenous_calculation(\ channel_amount=channel_amount, prepared_values=self.prepared_values, Nusselt_relations=Nusselt_relations, pressure_drop_relations=pressure_drop_relations, w_channel=w_channel, h_channel=h_channel, m_dot=m_dot, T_wall=T_wall, p_inlet=p_inlet, fp=fp, area_ratio_contraction=area_ratio_contraction) return super().setUp()
def run(F_desired, T_chamber, channel_amount, settings, x_guess): """ Algorithm to determine the thruster with the best specific impulse for the given thrust and power requirements Args: F_desired (N): Required thrust T_chamber (K): Chamber temperature determines mass flow and Isp and ideal power consumption channel_amount (-): Amount of channels. Technically, this must be optmized, but using built-in optimization really doesn't work well for integers In settings {dict}: FDP (dict): Fixed design parameters bounds (dict of 2-tuples): The constraints on the design parameters to be optimized NR (dict of functions): Nusselt relations that are used in various phases in the channel (liquid, two-phase, dry-out,gas) PDR (dict of functions): Pressure drop relations that are used in various phases in the channel (friction: liquid, two-phase, gas; contraction) steps (dict of ints): Fidelity of the temperature-steps in the various sections of the channel """ print("\n ----- OPTIMIZING THRUSTER FOR POWER CONSUMPTION -----") print("\n HIGH-LEVEL INPUTS") print("--- Desired thrust: {:3.1f} mN".format(F_desired * 1e3)) print("--- Chamber temperature: {:4.1f} K".format(T_chamber)) ## First determine ideal engine performance, and load settings for that T_inlet = settings['FDP'][ 'T_inlet'] # [K] Inlet temperature (at heating channel inlet manifold) p_inlet = settings['FDP'][ 'p_inlet'] # [Pa] Chamber pressure assumed equal to inlet pressure AR_exit = settings['FDP']['AR_exit'] # [-] Exit area ratio of nozzle p_back = settings['FDP']['p_back'] # [Pa] assert p_back == 0 # Nozzle correction does not work for back pressure fp = settings['FDP'][ 'fp'] # [object] FluidProperties object containing thermodynamic parameters for propellant print("--- Inlet pressure: {:2.1f} bar".format(p_inlet * 1e-5)) print("--- Exit area ratio {:2.1f}".format(AR_exit)) # Check if the inputs are correct assert (T_chamber > T_inlet) ep_ideal = IRT.engine_performance_from_F_and_T(F_desired=F_desired, p_chamber=p_inlet, T_chamber=T_chamber, AR_exit=AR_exit, p_back=p_back, fp=fp) # Calculate ideal power consumption m_dot = ep_ideal['m_dot'] # [kg/s] Mass flow through chamber Isp = ep_ideal['Isp'] # [s] Specific impulse P_ideal = chamber.ideal_power_consumption( # [W] Ideal power consumption mass_flow=m_dot, T_inlet=T_inlet, p_inlet=p_inlet, T_outlet=T_chamber, p_outlet=p_inlet, fp=fp) print("\n IDEAL PERFORMANCE") print("--- Nozzle status: {}".format(ep_ideal['nozzle_status'])) print("--- Mass flow: {:3.2f} mg/s".format(m_dot * 1e6)) print("--- Isp: {:3.1f} s".format(Isp)) print("--- Power consumption: {:2.1f} W".format(P_ideal)) # Calculate pre-calculated values of heating channel that are constant through entire optimization prepared_values = oneD.full_homogenous_preparation( T_inlet=T_inlet, # [K] Inlet temperature of the channel T_outlet= T_chamber, # [K] Outlet of the channel is equal to chamber temperature in IRT m_dot=m_dot / channel_amount, # [kg/s] Mass flow in a single channel p_ref= p_inlet, # [Pa] The pressure in the channel is assumed to be constant for heat flow calculation purposes steps_l=settings['steps'] ['steps_l'], # [-] Fidelity of simulation in liquid section of channel steps_tp=settings['steps'] ['steps_tp'], # [-] Fidelity of simulation in two-phase section of channel steps_g=settings['steps'] ['steps_g'], # [-] Fidelity of simulation in gas section of channel fp= fp, # [object] Object to access thermodynamic parameters of propellant with ) ### Define function to optimize here, and load settings into fixed design parameters ### IMPORTANT NOTE: if the order of arguments of f() is changed, the order of bounds must be changed too. # The bounds are defined a bit early here, so one does not forgot this after changing either. b = Bounds( [ # Lower bounds settings['bounds']['w_channel'][0], settings['bounds']['w_channel_spacing'][0], settings['bounds']['T_wall_superheat'][0] + T_chamber, ], [ # Higher bounds settings['bounds']['w_channel'][1], settings['bounds']['w_channel_spacing'][1], settings['bounds']['T_wall_superheat'][1] + T_chamber, ]) # Initial guess (just make it the higher bound (arbitary)) NOTE: SAME ORDER APPLIES ABOUNDS AND AS f() below x0 = None # Set the first guess based on a guess being provided or not if (x_guess is None): x0 = [ settings['bounds']['w_channel'][0], settings['bounds']['w_channel_spacing'][0], settings['bounds']['T_wall_superheat'][0] + T_chamber, ] else: x0 = x_guess # The function to optimize def f(x, return_full_results=False ): # NOTE: change bounds above if argument change if (return_full_results): print("Going to return full results.") w_channel = x[0] w_channel_spacing = x[1] T_wall = x[2] # Wall arguments need to be encapsulated in a dictionary so they can be conveniently passed to the section of code calcuating wall effects # and the actual bottom wall temperature wall_args = { 'kappa_wall': settings['FDP'] ['kappa_wall'], # [W/(m*K)] Thermal conductivity of chip wall 'h_channel': settings['FDP']['h_channel'], # [m] Channel depth, channel height 'w_channel': w_channel, # [m] Channel width 'w_channel_spacing': w_channel_spacing, # [m] Spacing between channels/wall thickness 'emissivity_chip_bottom': settings['FDP'] ['emissivity_chip_bottom'], # [m] Emissivity of the bottom of the chip } # Exit manifold length is zero based on existing VLM design, so should be zero in settings assert settings['FDP'][ 'l_exit_manifold'] == 0 # It is merely a remnant from previous code choices # The function that returns the eventual power output res_P = optim_P_total( # CAPITALIZED COMMENTS ARE FOR OPTIMIZED VARIABLES channel_amount=channel_amount, # [-] AMOUNT OF CHANNELS w_channel=w_channel, # [m] CHANNEL WIDTH h_channel=settings['FDP'] ['h_channel'], # [m] Channel depth, channel height inlet_manifold_length_factor=settings['FDP'] ['inlet_manifold_length_factor'], # [-] Linear factor to determine inlet manifold length inlet_manifold_width_factor=settings['FDP'] ['inlet_manifold_width_factor'], # [-] Linear factor to determine inlet manifold width l_exit_manifold=settings['FDP'] ['l_exit_manifold'], # [-] NOTE: old part of design. Is set to zero. w_channel_spacing= w_channel_spacing, # [m] SPACING BETWEEN CHANNELS/WALL THICKNESS w_outer_margin=settings['FDP'] ['w_outer_margin'], # [m] Margin around edge of heating channels, or nozzle width T_wall=T_wall, # [K] WALL TEMPERATURE p_ref=p_inlet, # [Pa] Pressure through channel m_dot=m_dot, # [kg/s] Heat flow through channel prepared_values= prepared_values, # {dict} Dictionary with many parameters that are costly to calcuate but are cosntant throughout optimization Nusselt_relations=settings[ 'Nusselt_relations'], # {dict} Heat transfer relations used for optimization pressure_drop_relations=settings[ 'pressure_drop_relations'], # {dict} Pressure drops used for optimization convergent_half_angle=settings['FDP'] ['convergent_half_angle'], # [rad] Half-angle of convergent part of 2D-conical nozzle divergent_half_angle=settings['FDP'] ['divergent_half_angle'], # [rad] Half-angle of divergent part of 2D-conical nozzle F_desired= F_desired, # [N] Desired thrust. Still required to correct for pressure drop p_back= p_back, # [Pa] Back pressure. NOTE: Nozzle correction depends on this being 0. AR_exit= AR_exit, # [-] Exit area ratio to correct nozzle after pressure drop emissivity_top=settings['FDP'] ['emissivity_chip_top'], # [-] Emissivity of top chip (black-body-radiation) fp= fp, # [object] Object to access thermodynamic parameters of propellant with wall_args= wall_args, # {dict} Arguments about wall design. See aboves ) # Return the total power consumption. The variable of interest P_total = P_ideal + res_P['P_loss'] #print(x) if math.isnan(P_total): #print("A constraint violated.") # Punish the objective function by outputting high power consumptions for invalid solutions # The punishment must however not be constant, or it will converge at the boundary where it is invalid. # Since the punishment stems from the pressure drop being higher than the initial p_chamber value, the lower the negative final pressure is the higher the punishment if return_full_results: print("Converged on invalid result!") print(res_P['pressure_drop_punishment']) return (P_ideal * (2 + 1 * res_P['pressure_drop_punishment']) ) * settings['function_scaling'] else: #print("P_total: {:2.5f} W".format(P_total)) # Scipy minimize can't handle multiple returns for the objective function, so the extensive final results must be pulled out afterwards if return_full_results: print("Returning full results!") print(res_P['P_loss']) return res_P else: return P_total * settings['function_scaling'] ## OPTIMIZATION ALGORITHM # First bounds must be set in the same order that arguments are given in f() # The order is: channel_amount, w_channel,w_channel_spacing,T_wall optimization_method = 'L-BFGS-B' optimization_options = { 'ftol': 2.220446049250313e-20, 'gtol': 1e-10, 'disp': False, } # Pressure drop constraint con_pressure_drop = NonlinearConstraint(f, 0, 2 * P_ideal) minimize_results = minimize(fun=f, x0=x0, bounds=b, method=optimization_method, options=optimization_options) # print(minimize_results.x) print(minimize_results.success) print(minimize_results.status) print(minimize_results.message) print(minimize_results.nit) print("Trying to return full results") res_final = f(minimize_results.x, return_full_results=True) print(res_final['P_loss']) optim_results = { #'full_res': res_final['res'], #'full_prepared_values': res_final['prepared_values'], 'minimize_results': minimize_results, 'w_channel': minimize_results.x[0], 'w_channel_spacing': minimize_results.x[1], 'T_wall_top': minimize_results.x[2], 'P_total': minimize_results.fun / settings['function_scaling'], 'P_loss': res_final['P_loss'], 'P_ideal': P_ideal, 'l_channel': res_final['l_channel'], 'h_channel': res_final['h_channel'], 'hydraulic_diameter': res_final['hydraulic_diameter'], 'l_inlet': res_final['l_inlet'], 'l_total': res_final['l_total'], 'A_chip': res_final['A_chip'], 'l_outlet': res_final['l_outlet'], 'l_convergent': res_final['l_convergent'], 'l_divergent': res_final['l_divergent'], 'l_channel_l': res_final['l_channel_l'], 'l_channel_tp': res_final['l_channel_tp'], 'l_channel_g': res_final['l_channel_g'], 'T_wall_bottom': res_final['T_wall_bottom'], 'w_total': res_final['w_total'], 'w_channels_total': res_final['w_channels_total'], 'w_nozzle': res_final['w_nozzle'], 'w_inlet': res_final['w_inlet'], 'pressure_drop': res_final['pressure_drop'], 'Re_channel_l': res_final['Re_channel_l'], 'Re_channel_tp': res_final['Re_channel_tp'], 'Re_channel_g': res_final['Re_channel_g'], 'M_channel_exit_after_dP': res_final['M_channel_exit_after_dP'], 'Re_channel_exit_after_dP': res_final['Re_channel_exit_after_dP'], 'm_dot': ep_ideal['m_dot'], 'Isp': ep_ideal['Isp'], 'p_inlet': p_inlet, 'w_throat_new': res_final['w_throat_new'], 'Re_throat_new': res_final['Re_throat_new'], } #print(optim_results) return optim_results
def calc_and_plot_channel_width(w_channel, ax_P_loss, fig_P_loss, ax_w_total, fig_w_total, ax_l_total, fig_l_total, ax_pressure_drop, fig_pressure_drop, ax_is_choked, fig_is_choked,\ ax_Re_channel_exit, fig_Re_channel_exit): # Taking some thruster data to make the optmization a bit similar to existing cases td = None #thrusters.thruster_data.Cen2010_6 # Desired chamber outlet/nozzle inlet conditions # Switch between an existing thruster or a desired one if td is not None: m_dot = td['m_dot'] # [kg/s] Mass flow T_chamber = td['T_chamber_guess'] # [K] Chamber temperature T_inlet = 300 # [K] Inlet temperature p_inlet = td[ 'p_inlet'] # [Pa] Inlet pressure, assumed to be constant through-out the channel F_desired = 7.71e-3 # td['F'] # [N] Thrust p_back = td['p_back'] fp = FluidProperties( td['propellant']) # Object to access fluid properties AR_exit = td['AR_exit'] # [-] Exit area of nozzle w_throat = td['w_throat'] # [m] Throat width #w_channel = 1.7*td['w_channel'] # [m] Channel width 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 = 1e-3 # [m] Length between the end of multiple channels and start of convergent nozzle convergent_half_angle = td[ 'convergent_half_angle'] # [rad] Half-angle of the convergent part of the nozzle divergent_half_angle = td[ 'divergent_half_angle'] # [rad] Half-angle of divergent part of the nozzle w_channel_spacing = td[ 'w_channel_spacing'] # [m] Spacing between channels (wall-to-wall) w_outer_margin = 2e-3 # [m] Margin around the outer channels for structural integrity channel_amount = td['channel_amount'] # [-] Number of channels h_channel = td['h_channel'] # [m] Channel depth emissivity_chip_top = 1 # [-] Assumed emissivity of chip at top-side else: F_desired = 10e-3 # [N] Desired thrust p_inlet = 5.0e5 # [Pa] Inlet pressure T_inlet = 300 # [K] Inlet temperature T_chamber = 500 # [K] Chamber temperature p_back = 0 # [Pa] Back pressure AR_exit = 10 # [-] Exit area ratio fp = FluidProperties( "water") # (obj) Object to access water properties h_channel = 100e-6 # [m] Channel depth #w_channel = 25e-6 # [m] Channel width # Find the mass flow and throat area IRT requires ep_ideal = engine_performance_from_F_and_T(F_desired=F_desired, p_chamber=p_inlet, T_chamber=T_chamber, AR_exit=AR_exit, p_back=p_back, fp=fp) m_dot = ep_ideal['m_dot'] # [kg/s] Mass flow w_throat = ep_ideal['A_throat'] / h_channel # [m] Throat width Isp = ep_ideal['thrust'] / ep_ideal['m_dot'] / physical_constants.g0 # Print ideal values print("\n --- IDEAL PARAMETERS ---") print(" Thrust: {:1.1f} mN".format(ep_ideal['thrust'] * 1e3)) print(" Isp: {:3.1f} s".format(Isp)) print(" Mass flow: {:3.3f} mg/s".format(m_dot * 1e6)) print(" Throat width: {:4.1f} micron".format(w_throat * 1e6)) # Remaining paramters 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 divergent_half_angle = math.radians( 22.5) # [rad] Half-angle of divergent part of the nozzle w_channel_spacing = 0.5e-5 # [m] Spacing between channels (wall-to-wall) w_outer_margin = 2e-3 # [m] Margin around the outer channels for structural integrity channel_amount = 20 # [-] Number of channels emissivity_chip_top = 0.5 # [-] Assumed emissivity of chip at top-side emissivity_chip_bottom = 0.5 # [-] # Wall effects kappa_wall = 100 # [W/(m*K)]Thermal conductivity of the silicon walls #T_wall_bottom = T_chamber wall_args = { 'kappa_wall': kappa_wall, # 'T_wall_bottom': T_wall_bottom, 'h_channel': h_channel, 'w_channel': w_channel, 'w_channel_spacing': w_channel_spacing, 'emissivity_chip_bottom': emissivity_chip_bottom, } # Desired geometric values # Used heat transfer relations Nusselt_relations = { 'Nu_func_gas': thermo.convection. Nu_laminar_developed_constant_wall_temp_square, # [-] Function to calculate Nusselt number (gas phase) 'Nu_func_liquid': thermo.convection. Nu_laminar_developed_constant_wall_temp_square, # [-] Function to caculate Nusselt number (liquid phase) 'Nu_func_two_phase': tp. Nu_Kandlikar_NBD_dryout, # [-] Function to calculate Nusselt number (two-phase) 'Nu_func_le': thermo.convection. Nu_laminar_developed_constant_wall_temp_square, # [-] Function to calculate Nusselt in two-phase, AS IF the flow was entirely liquid (two-phase, le) 'Nu_func_dryout': thermo.convection. Nu_laminar_developed_constant_wall_temp_square, #thermo.two_phase.Nu_DB_two_phase # [-] Function to calculate the Nusselt number after dry-out. It is up to Nu_func_two_phase to decide if/how to apply it' } pressure_drop_relations = { 'l': oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds, 'tp': oneD.calc_two_phase_frictional_pressure_drop_low_Reynolds, 'g': oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds, 'contraction': oneD.calc_single_phase_contraction_pressure_drop_Kawahara2015 } # Fidelity of simulation steps_per_section = 100 # [-] Amount of subdivision in each section steps_l = steps_per_section steps_tp = steps_per_section steps_g = steps_per_section prepared_values = oneD.full_homogenous_preparation( T_inlet=T_inlet, T_outlet= T_chamber, # Outlet of the channel is equal to chamber temperature in IRT m_dot=m_dot / channel_amount, p_ref=p_inlet, # The pressure in the channel is assumed to be constant steps_l=steps_l, steps_tp=steps_tp, steps_g=steps_g, fp=fp) # Power loss function to optimzie func_P_loss = lambda T_superheat: optim_P_total( channel_amount=channel_amount, w_channel=w_channel, h_channel=h_channel, inlet_manifold_length_factor=inlet_manifold_length_factor, inlet_manifold_width_factor=inlet_manifold_width_factor, l_exit_manifold=l_exit_manifold, w_channel_spacing=w_channel_spacing, w_outer_margin=w_outer_margin, T_wall=T_chamber + T_superheat, # [K] Wall temperature must of course be higher than chamber temperature, so a variable T_superheater that must be larger than 0 is useful p_ref=p_inlet, m_dot=m_dot, prepared_values=prepared_values, Nusselt_relations=Nusselt_relations, pressure_drop_relations=pressure_drop_relations, convergent_half_angle=convergent_half_angle, divergent_half_angle=divergent_half_angle, F_desired=F_desired, p_back=p_back, AR_exit=AR_exit, w_throat=w_throat, emissivity_top=emissivity_chip_top, fp=fp, wall_args=wall_args) # Fist make a plot to see what's going on T_range = np.linspace(start=50, stop=200, num=200) # [K] # Store calculated heat loss here P_loss = np.zeros_like(T_range) * np.nan # [W] Heat loss l_channel = np.zeros_like( T_range) * np.nan # [m] Heating channel length (not the chip length) l_total = np.zeros_like(T_range) * np.nan # [m] Total chip length w_total = np.zeros_like(T_range) * np.nan # [m] Total chip width. pressure_drop = np.zeros_like( T_range ) * np.nan # [Pa] Pressure drop over channel (only if it drops stagnation pressure) is_channel_choked = np.zeros_like( T_range) * np.nan # [-] Going to be 0 if not choked, and 1 if chocked Re_channel_exit = np.zeros_like( T_range) * np.nan # [-] Reynolds number at throat dP_contraction = np.zeros_like( T_range ) * np.nan # [Pa] Pressure drop over sudden contraction at entrance #F = np.zeros_like(T_range) # [N] To store thrust that is iterated to, for debugging purposes #CF = np.zeros_like(T_range) # [-] To store thrust coefficient iter_T = np.nditer(T_range, flags=['c_index']) for T in iter_T: #print("\n\n --- RUN {} ---- ".format(iter_T.index)) res = func_P_loss(T) P_loss[iter_T.index] = res['P_loss'] l_channel[iter_T.index] = res['l_channel'] pressure_drop[iter_T.index] = res['pressure_drop'] dP_contraction[iter_T.index] = res['dP_contraction'] l_total[iter_T.index] = res['l_total'] w_total[iter_T.index] = res['w_total'] # Check if channels are choked? is_channel_choked[iter_T.index] = res['is_channel_choked'] # [] # See if Reynolds number still makes sense (laminar or turbulent?) Re_channel_exit[iter_T.index] = res['Re_channel_exit'] print("Minumum heat loss: {:2.3f} W".format(np.nanmin(P_loss))) #ax2 = ax1.twinx() ax_P_loss.plot(T_range + T_chamber, P_loss, label="{:3.1f}".format(w_channel * 1e6)) fig_P_loss.suptitle( "Heat loss ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber)) ax_P_loss.set_xlabel("Wall temp [K]") ax_P_loss.set_ylabel("Heat loss [W]") ax_l_total.plot(T_range + T_chamber, l_channel * 1e3, label="{:3.1f}".format(w_channel * 1e6)) ax_l_total.set_ylabel("Channel length [mm]") ax_l_total.set_xlabel("Wall temp [K]") fig_l_total.suptitle( "Channel length ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber)) ax_pressure_drop.plot(T_range + T_chamber, pressure_drop * 1e-5, label="{:3.1f}".format(w_channel * 1e6)) ax_pressure_drop.set_xlabel("Wall temp [K]") ax_pressure_drop.set_ylabel("Pressure drop [bar]") fig_pressure_drop.suptitle( "Pressure drop ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber)) ax_is_choked.plot(T_range + T_chamber, is_channel_choked, marker='o', linestyle=" ", label="{:3.1f}".format(w_channel * 1e6)) ax_is_choked.set_xlabel("Wall temp [K]") ax_is_choked.set_ylabel("Choked ?") fig_is_choked.suptitle( "Choked? ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber)) ax_w_total.plot(T_range + T_chamber, w_total * 1e3, label="{:3.1f}".format(w_channel * 1e6)) ax_w_total.set_xlabel("Wall temp [K]") ax_w_total.set_ylabel("Chip width [mm] ") fig_w_total.suptitle( "Chip width ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber)) ax_Re_channel_exit.plot(T_range + T_chamber, Re_channel_exit, label="{:3.1f}".format(w_channel * 1e6)) ax_Re_channel_exit.set_xlabel("Wall temp [K]") ax_Re_channel_exit.set_ylabel("Reynolds Channel [mm] ") fig_Re_channel_exit.suptitle( "Reynolds channel($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)" .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))
def calc_and_plot_thruster(td, axs_to_plot): # For Cen the chamber temperature is unknown, so a range is taken instead # seem inconsitent with saturation temperatures and/or reported wall temperatures T_wall = td['T_wall'] # [K] Wall temperature w_channel = td['w_channel'] # [m] Channel width T_inlet = td['T_inlet'] # [K] Inlet temperature p_inlet = td['p_inlet'] # [Pa] Inlet pressure m_dot = td['m_dot'] # [kg/s] Mass flow (through all channels if multiple) channel_amount = td['channel_amount'] # [-] Amount of channels h_channel = td['h_channel'] # [m] Channel height/depth fp = FluidProperties(td['propellant']) # Object from which fluid properties can be accessed # Calculate mass flow for one single channel m_dot_channel = m_dot/channel_amount # [kg/s] Mass flow through one single channel # Chamber temperature is unknown, so a range is taken T_sat = fp.get_saturation_temperature(p=p_inlet) # [K] T_chamber = np.linspace(start=T_sat+1, stop=T_wall-1, num=250) # [K] # Geometric values wetted_perimeter = basic.chamber.wetted_perimeter_rectangular(w_channel=w_channel, h_channel=h_channel) # [m] Wetted perimeter of channel A_channel = w_channel*h_channel # [m^2] Cross-sectional through which fluid flows D_hydraulic = basic.chamber.hydraulic_diameter_rectangular(w_channel=w_channel, h_channel=h_channel) # [m] Hydraulic diameter # Preparation functions calculations many intermediate values that are known before geometry is known # Storing the length results in here, one for each set of Nusselt relations L_1 = np.zeros_like(T_chamber) # [m] Total channel length L_2 = np.zeros_like(L_1) # Loop to calculate the channel length with each wall temperature it_T = np.nditer(T_chamber, flags=['c_index']) # [K] Wall temperature T_chamber_guess = None # [K] Stores chamber temperature when actual length is first reached (in laminar case). for T in it_T: prepared_values = oneD.full_homogenous_preparation( T_inlet=T_inlet, T_outlet=T, # <---- Iterated variable m_dot=m_dot_channel, p_ref=p_inlet, steps_l=steps_l, steps_tp=steps_tp, steps_g=steps_g, fp=fp) # results_1 = oneD.full_homogenous_calculation( # prepared_values=prepared_values, # Nusselt_relations=Nusselt_relations_1, # A_channel=A_channel, # wetted_perimeter=wetted_perimeter, # D_hydraulic=D_hydraulic, # m_dot=m_dot, # T_wall=T_wall, # p_ref=p_inlet, # fp=fp # ) results_2 = oneD.full_homogenous_calculation( prepared_values=prepared_values, Nusselt_relations=Nusselt_relations_2, A_channel=A_channel, wetted_perimeter=wetted_perimeter, D_hydraulic=D_hydraulic, m_dot=m_dot_channel, T_wall=T_wall, # <--- Iterated variable p_ref=p_inlet, fp=fp ) # First time the length crosses the actual length, store the value if (T_chamber_guess == None) and (results_2['L_total'] > td['L_channel']): # results_2 stores laminar results T_chamber_guess = T # [K] # L_1[it_T.index] = results_1['L_total'] L_2[it_T.index] = results_2['L_total'] ## Print info about thruster, including, estimated chamber temperature for laminar relations print("Thruster name: {}".format(td['name'])) print("Estimated chamber temperature: {:3.2f} K".format(T_chamber_guess)) axs_to_plot.set_title("$\\dot{{m}}={:1.2f}$ mg/s, $p={:1.2f}$ bar".format(m_dot*1e6,p_inlet*1e-5)) # axs_to_plot.plot(T_chamber,L_1*1e3, label="Turbulent") axs_to_plot.plot(T_chamber,L_2*1e3, label="Laminar") axs_to_plot.hlines(td['L_channel']*1e3, xmin=T_chamber[0], xmax=T_chamber[-1], linestyle='dashed', color='red', label="Real length") axs_to_plot.grid()
# Wall temperature is unknown, so a range is taken T_wall = np.linspace(start=T_chamber + 0.01, stop=600, num=5000) # [K] # Geometric values wetted_perimeter = basic.chamber.wetted_perimeter_rectangular( w_channel=w_channel, h_channel=h_channel) # [m] Wetted perimeter of channel A_channel = w_channel * h_channel # [m^2] Cross-sectional through which fluid flows D_hydraulic = basic.chamber.hydraulic_diameter_rectangular( w_channel=w_channel, h_channel=h_channel) # [m] Hydraulic diameter # Preparation functions calculations many intermediate values that are known before geometry is known prepared_values = oneD.full_homogenous_preparation(T_inlet=T_inlet, T_outlet=T_chamber, m_dot=m_dot, p_ref=p_inlet, steps_l=steps_l, steps_tp=steps_tp, steps_g=steps_g, fp=fp) # Storing the length results in here, one for each set of Nusselt relations L_1 = np.zeros_like(T_wall) # [m] Total channel length L_2 = np.zeros_like(L_1) # Loop to calculate the channel length with each wall temperature it_T = np.nditer(T_wall, flags=['c_index']) # [K] Wall temperature for T in it_T: # results_1 = oneD.full_homogenous_calculation( # prepared_values=prepared_values, # Nusselt_relations=Nusselt_relations_1,