def total_power_single_channel(T_wall, w_channel, Nu_func, T_inlet, T_chamber, T_ref, p_ref, m_dot, h_channel, w_channel_margin, emmisivity, fp): """ 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 (-): Nusselt function T_inlet (K): Chamber inlet temperature T_chamber (K): Chamber outlet temperature (same as T_c in IRT) T_ref (K): Reference temperature for the Nusselt relation and flow similary parameters 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 emmisivity (-) Emmisivity of the chamber material (for calculation of radation losses) fp (- ): [description] """ ## NOTE: just a test, assumptions are a bit sloppy, and this is just to test optimization approach at this stage A_channel = w_channel * h_channel # [m^2] Channel area print("A_channel: {}".format(A_channel)) wetted_perimeter = wetted_perimeter_rectangular( w_channel=w_channel, h_channel=h_channel ) # [m] Distance of channel cross-section in contact with fluid print("wetted_perimeter: {}".format(wetted_perimeter)) # Reference length is hydraulic diameter D_hydraulic = hydraulic_diameter_rectangular( w_channel=w_channel, h_channel=h_channel) # [m] Hydraulic diameter cp = chamber_performance_from_Nu(Nu_func=Nu_func, T_inlet=T_inlet, T_chamber=T_chamber, T_ref=T_ref, T_wall=T_wall, p_ref=p_ref, m_dot=m_dot, A_channel=A_channel, L_ref=D_hydraulic, fp=fp) ## Assume single-sided heater heat chamber material through-out at T-wall, and all along the wetted perimeter heat flows equally to teh propellant A_heater = cp['A_heater'] # [m^2] assert (A_heater > 0) L_channel = A_heater / wetted_perimeter # [m] Required length of channel to achieve required heat flow print("L_channel: {}".format(L_channel)) A_chip = required_chip_area( L_channel=L_channel, w_channel=w_channel, w_channel_margin=w_channel_margin ) # [m^2] Assume radiation occurs from silicon on just a single side of the heater print("A_microheater: {}".format(A_chip)) P_radiation = radiation_loss(T=T_wall, A=A_chip, emmisivity=emmisivity) # [W] Radiation loss P_total = P_radiation + cp[ 'Q_dot'] # [W] Total power consumption, the variable to minimize print("Heat loss {}".format(P_radiation / P_total)) return P_total
def test_simple_input(self): # inputs of 1 should return stefan-boltzmann constant emmisivity = 1 A = 1 T = 1 exp = 5.670374419e-8 # [W/(m^2*K^4)] Source: https://en.wikipedia.org/wiki/Stefan%E2%80%93Boltzmann_constant res = chamber.radiation_loss(T=T, A=A, emmisivity=emmisivity) self.assertEqual(exp, res) emmisivity = 0.5 exp = 0.5 * exp res = chamber.radiation_loss(T=T, A=A, emmisivity=emmisivity) self.assertEqual(exp, res) A = 4 exp = 4 * exp res = chamber.radiation_loss(T=T, A=A, emmisivity=emmisivity) self.assertEqual(exp, res) T = 2 exp = 16 * exp res = chamber.radiation_loss(T=T, A=A, emmisivity=emmisivity) self.assertEqual(exp, res)
def calc_bottom_plane_heat_balance(h_conv, T_fluid, we, wall_args, delta_L): """Calculates the total heat transfer from the bottom wall to the fluid, and through the wall at lowest wall section. This function is necessary, as the bottom wall temperature is only solved if the radiation loss completes the heat balance Args: we (dict): Dictionary containing all resulting wall effect parameters wall_args(dict): Dictionary containing input parameters for wall effect calculations dL (m): Length of wall section Returns: Q_heat_transfer (W): total heat transfer from the bottom wall to the fluid, and through the wall at lowest wall section """ # print("\nT_wall_bottom: {:3.1f} K".format(wall_args['T_wall_bottom'])) # print("H_conv:") # print(h_conv) # print("Grad_theta_L:") # print(we['grad_theta_L']) # print("Delta_L:") # print(delta_L) Q_conduction_in = -wall_args['kappa_wall']*we['grad_theta_L']*wall_args['w_channel_spacing']*delta_L # [W] Heat flowing in through wall conduction # print(" --- Q_conduction_in: ---") # print(Q_conduction_in) Q_convection_out = h_conv*(wall_args['T_wall_bottom']-T_fluid)*wall_args['w_channel']*delta_L # [W] Heat flowing from bottom wall to fluid through convection dA_bottom = (wall_args['w_channel']+wall_args['w_channel_spacing'])*delta_L Q_radiation_out = radiation_loss(T=wall_args['T_wall_bottom'],A=dA_bottom,emmisivity=wall_args['emissivity_chip_bottom']) # [W] Heat radiated from bottom of chip # Sum the totals to report the (im)balance Q_conduction_total = np.sum(Q_conduction_in) # [W] Q_convection_total = np.sum(Q_convection_out) # [W] Q_radiation_total = np.sum(Q_radiation_out) # [W] # print("Q_conduction_total: {:3.1f} W".format(Q_conduction_total)) # print("Q_convection_total: {:3.1f} W".format(Q_convection_total)) # print("Q_radiation_total: {:3.1f} W".format(Q_radiation_total)) return (Q_conduction_total - Q_convection_total - Q_radiation_total) # [W] Heat difference that must be compensated by radiation loss for the solution to be valid
T_outlet=T_chamber, p_outlet=p_inlet, fp=fp) # [J/kg] Specific enthalpy change of the channel Q_dot = required_power( m_dot=m_dot, delta_h=delta_h ) # [W] Power that must go into the flow to achieve delta_h A_heater[it_T.index, :] = required_heater_area( Q_dot=Q_dot, h_conv=h_conv[it_T.index, :], T_wall=T, T_ref=T_bulk) # [m^2] Required area to deliver power Q_dot L_channel[it_T.index, :] = A_heater[ it_T. index, :] / w_channel # [m] With this length , the required heater area is obtained for the given channel width # Estimated power loss through radation # Under the simple assumption that the heater only loses power through radiation, the power loss is calculated P_radiation_loss[it_T.index, :] = radiation_loss( T=float(T), A=A_heater[it_T.index, :], emmisivity=emmisivity ) # [W] Radiation from single-sided heater surface P_total[it_T.index, :] = P_radiation_loss[it_T.index, :] + Q_dot ## PLOTTING RESULTS fig0, axs0 = plt.subplots(3, 1) # For all the flow similarity parameters fig1, axs1 = plt.subplots(3, 1) # For the resulting dimensions and power loss # First plot the results that do not vary with T_chamber, and need only be plotted once # Reset the iterator to plot all results with different lines for different chamber temperatures it_T.reset() for T in it_T: ## First set of subplots # Bulk velocity axs0[0].plot(w_channel * 1e6, u_bulk[it_T.index, :],
def optim_P_total(channel_amount, w_channel, h_channel, inlet_manifold_length_factor, inlet_manifold_width_factor, l_exit_manifold, w_channel_spacing, w_outer_margin, T_wall, p_ref, m_dot, \ prepared_values, Nusselt_relations, pressure_drop_relations, convergent_half_angle, divergent_half_angle, F_desired, p_back, AR_exit, w_throat, emissivity_top, fp: FluidProperties, wall_args=None): # First calculate area ratio at entrance so it can be provided to contraction pressure drop function w_inlet_manifold = chamber.inlet_manifold_width(\ channel_amount=channel_amount, w_channel=w_channel, w_channel_spacing=w_channel_spacing, inlet_manifold_width_factor=inlet_manifold_width_factor) # [m] Total width of inlet manifold area_ratio_contraction = chamber.area_ratio_contraction(\ w_inlet_manifold=w_inlet_manifold, w_channel=w_channel, channel_amount=channel_amount) # [-] Area ratio of sudden contraction def x(T): wall_args[ 'T_wall_bottom'] = T # [K] Add the bottom wall temperature in the wall argument dictionary # Calculate heat transfer to determine channel length res = oneD.rectangular_multi_channel_homogenous_calculation(\ channel_amount=channel_amount, prepared_values=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_ref, fp=fp, area_ratio_contraction=area_ratio_contraction, wall_args=wall_args) #print(res['Q_total_bottom_plane_heat_balance']) return res['Q_total_bottom_plane_heat_balance'] ## ROOT FINDING PROBLEM FOR T_WALL_BOTTOM # The proper bounds for the optimization are very sensitive to wall temperature and chamber temperature # It is hard to pick bounds that are valid for a wide range of those parameters # The problem is that that a too low lower bound results in model results that are wholly invalid and break the optimization # Too high a lower bound results in the solution not being within the bounds at all. # The quick and dirty solution here is to stepwise move the bounds down until the valid solution is between it. # The stepsize must be small enough to reliably avoid the scenario where the calculations are completely incorrect. delta_T_bounds = 25 # [K] The size of the bound shift could determine how low the bounds can go to the final chamber temperature without resulting into incorrection calculations ## The resultant channel length, pressure drops, etc, depends on bottom wall temperature. It must be iterated towards here. T_wall_bottom_min = T_wall - delta_T_bounds # [K] Minimum temperature is in practice probably not lower than chamber temperature (not impossible, though) T_wall_bottom_max = T_wall bounds_are_correct = False # [bool] Is False until a solution has been found # The bounds are correct once they get the opposite sign while not bounds_are_correct: a = x(T_wall_bottom_max) b = x(T_wall_bottom_min) # If the solutions have the same sign, subtract delta_T_bounds if np.sign(a) == np.sign(b): T_wall_bottom_min -= delta_T_bounds T_wall_bottom_max -= delta_T_bounds else: bounds_are_correct = True root_result = root_scalar(x, bracket=([T_wall_bottom_min, T_wall_bottom_max])) if root_result.converged: T_wall_bottom = root_result.root # [m^2] wall_args['T_wall_bottom'] = T_wall_bottom #print(" T_wall_bottom: {:3.2f} K".format(T_wall_bottom)) else: raise Exception("No solution found for given temperature") res = oneD.rectangular_multi_channel_homogenous_calculation(\ channel_amount=channel_amount, prepared_values=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_ref, fp=fp, area_ratio_contraction=area_ratio_contraction, wall_args=wall_args) # Check if these solutions are eventually valid assert (np.all(res['res_l']['delta_L'] >= 0)) assert (np.all(res['res_tp']['delta_L'] >= 0)) assert (np.all(res['res_g']['delta_L'] >= 0)) l_channel = res['L_total'] # [m] Channel length p_chamber = res[ 'p_chamber'] # [Pa] Pressure out channel outlet/inlet of nozzle according to IRT T_chamber = res['T_chamber'] # Nozzle performance must be recalculated, if pressure drop occured assert (pressure_drop_relations is not None) # Just check is one was calculated if p_chamber <= 0: # If pressure drop was so large p_chamber was negative, the solution is invalid, and NaNs must be returned # Return the same dictionary, but all NaN. return { 'P_loss': np.nan, # [W] Current approximation of heat loss 'l_channel': np.nan, # [m] Channel length 'pressure_drop': np.nan, # [Pa] Total pressure drop 'dP_contraction': np.nan, # [Pa] Pressure drop de to contraction 'w_total': np.nan, # [m] Total chip width 'l_total': np.nan, # [m] Total chip length 'is_channel_choked': np.nan, 'Re_channel_exit': np.nan, # [-] Reynolds number at channel exit } # This only runs if pressure drop was not too large. # In that case the throat area must be recalculated still else: ep = engine_performance_from_F_and_T(F_desired=F_desired, p_chamber=p_chamber, T_chamber=T_chamber, AR_exit=AR_exit, p_back=p_back, fp=fp) # New throat widtl_inlet_manifoldmined from desired performance parameter w_throat_new = ep['A_throat'] / h_channel # [m] # Check if new pressure, does not cause choked heating channels is_channel_choked = ( w_channel * h_channel * channel_amount < ep['A_throat'] ) # [bool] If combined channel area is smaller than throat, they are choked. # Check new Reynolds number at channel exit for laminar/turbulent flow assumptions hydraulic_diameter = chamber.hydraulic_diameter_rectangular( w_channel=w_channel, h_channel=h_channel) Re_channel_exit = fp.get_Reynolds_from_mass_flow( T=T_chamber, p=p_chamber, L_ref=hydraulic_diameter, m_dot=m_dot, A=h_channel * w_channel) l_outlet = chamber.outlet_length(\ w_channel=w_channel, w_channel_spacing=w_channel_spacing, channel_amount=channel_amount, convergent_half_angle=convergent_half_angle, w_throat=w_throat_new, divergent_half_angle=divergent_half_angle, AR_exit=AR_exit, l_exit_manifold=l_exit_manifold ) l_inlet_manifold = chamber.inlet_manifold_length( w_inlet_manifold=w_inlet_manifold, inlet_manifold_length_factor=inlet_manifold_length_factor) l_total = chamber.total_chip_length(l_inlet_manifold=l_inlet_manifold, l_channel=l_channel, l_outlet=l_outlet) w_total = chamber.total_chip_width(\ w_inlet_manifold=w_inlet_manifold, w_outer_margin=w_outer_margin, w_throat=w_throat_new, AR_exit=AR_exit) A_chip = l_total * w_total # [m^2] P_rad_loss_top = chamber.radiation_loss( T=T_wall, A=A_chip, emmisivity=emissivity_top ) # [W] Radiation loss through top of chip P_rad_loss_bottom = chamber.radiation_loss( T=wall_args['T_wall_bottom'], A=A_chip, emmisivity=wall_args['emissivity_chip_bottom'] ) # Radiation loss through bottom of chip P_loss = P_rad_loss_top + P_rad_loss_bottom # [W] Total heat loss return { 'P_loss': P_loss, # [W] Current approximation of heat loss 'l_channel': l_channel, # [m] Channel length 'pressure_drop': res['dP_total'], # [Pa] Total pressure drop 'dP_contraction': res['dP_contraction'], # [Pa] Pressure drop due to contraction 'w_total': w_total, # [m] Total chip width 'l_total': l_total, # [m] Total chip length 'is_channel_choked': is_channel_choked, # [bool] 0 and 1 in this case, to check if channels are not too small 'Re_channel_exit': Re_channel_exit, # [-] Check Reynolds number to see if laminar flow assumptions still hold }