def set_design_static(gamma, p_chamber, p_exit, c_star, OF, ox_flow): """ Static method, return the main nozzle parameters for de given design conditions. :param gamma: flow isentropic parameter :param p_chamber: chamber pressure in Pascals :param p_exit: desired exit pressure (ie atmospheric pressure) in Pascals :param c_star: expected combustion c* :param ox_flow: flow of oxidizer into the chamber """ # Determination of parameters based on isoentropic flow and c* definition throat_area = c_star * ox_flow * (1 + 1 / OF) / p_chamber exit_mach = iso.mach_via_pressure(gamma, p_chamber, p_exit) expansion_ratio = iso.expansion_via_mach(gamma, exit_mach) exit_area = throat_area * expansion_ratio return throat_area, expansion_ratio, exit_area
def set_design_static(gamma, p_chamber, p_exit, c_star, isp, thrust): """ Static method, return the main nozzle parameters for de given design conditions. :param gamma: flow isentropic parameter :param p_chamber: chamber pressure in Pascals :param p_exit: desired exit pressure (ie atmospheric pressure) in Pascals :param c_star: expected combustion c* :param isp: expected rocket specific impulse in seconds :param thrust: expected rocket thrust """ # Determination of parameters based on isentropic flow and c* definition throat_area = c_star * thrust / isp / 9.81 / p_chamber exit_mach = iso.mach_via_pressure(gamma, p_chamber, p_exit) expansion_ratio = iso.expansion_via_mach(gamma, exit_mach) exit_area = throat_area * expansion_ratio return throat_area, expansion_ratio, exit_area
def run_balanced_nozzle_analysis(self, ox_flow, safety_thickness, dt, max_burn_time=None, tol_press=1e-3, **kwargs): # Check the inputs assert ox_flow > 0, "Oxidizer flow not greater than 0, check your inputs to CombustionModule. \n" assert safety_thickness > 0, "Safety thickness not greater than 0, check your inputs to CombustionModule. \n" assert dt > 0, "Simulation time-increment not greater than 0, check your inputs to CombustionModule. \n" # ------------------- Set the output flag: if 'file_name' in kwargs: flag_output = True else: flag_output = False # Call for workspace definition methods g0, R, pression_atmo, a, n, m, rho_fuel, initial_chamber_pressure, eta_comb = self.unpack_data_dictionary() # -------------------- Initialize simulation_variables if required ------------- pression_chambre = initial_chamber_pressure # ---------------------------- MAIN SIMULATION LOOP ----------------------------- # Set a counter to keep-track of the loop k = 1 # Set the flag for the maximum burn-time flag_burn = True while self.geometry.min_bloc_thickness() > safety_thickness and flag_burn: # ------------------- Perform block solution with pressure coupling --------------------- solution, of_ratio, m_ox, m_fuel, total_mass_flow,\ g_ox, g_f, pression_chambre, t_chamber, gamma, \ gas_molar_mass, cea_c_star, son_vel, rho_ch = self._solve_pressure_loop(pression_chambre, ox_flow, rho_fuel, tol_press) # ----------------------- Perform the regression of the block --------------- self.geometry.regress(solution, self.regression_model, ox_flow, dt) r = R / gas_molar_mass # Specific gaz constant # Determine the flow port exit-speed u_ch = calculate_flow_speed(cross_section=self.geometry.mesh.cells[-1].return_area_data(), mass_flow=total_mass_flow, density=rho_ch) # ------------------------- Determine Propulsion Performances ------------------ mach_exit = iso.exit_mach_via_expansion(gamma=gamma, expansion=self.nozzle.get_expansion_ratio(), supersonic=True) exit_pressure = pression_chambre / iso.pressure_ratio(gamma, mach_exit) t_exit = t_chamber / iso.temperature_ratio(gamma, mach_exit) v_exit = math.sqrt(gamma * r * t_exit) * mach_exit thrust = self.nozzle.get_nozzle_effeciency() * ( total_mass_flow * v_exit + (exit_pressure - pression_atmo) * self.nozzle.get_exit_area()) # Calculate the nozzle equation validation nozzle_p = total_mass_flow * cea_c_star / (self.nozzle.get_throat_area() * pression_chambre) isp = thrust / total_mass_flow / g0 # ------------------------------ Results updates -------------------------------- self.results['run_values']['time'].append(k * dt) self.results["run_values"]["thrust"].append(thrust) self.results["run_values"]["isp"].append(isp) self.results["run_values"]["pressure"].append(pression_chambre) self.results["run_values"]["temperature"].append(t_chamber) self.results["run_values"]["of"].append(of_ratio) self.results["run_values"]["Go"].append(g_ox) self.results["run_values"]["nozzle_param"].append(nozzle_p) self.results["run_values"]["c_star"].append(cea_c_star) self.results["run_values"]["chamber_sound_speed"].append(son_vel) self.results["run_values"]["chamber_rho"].append(rho_ch) self.results["run_values"]["mass_flow"].append(total_mass_flow) self.results["run_values"]["mass_flow_ox"].append(ox_flow) self.results["run_values"]["mass_flow_f"].append(m_fuel) self.results["run_values"]["chamber_speed"].append(u_ch) # ------------------------- Output the Geometry to file ----------------------- if flag_output: if k * dt in kwargs['times']: self.geometry.print_geometry_to_file(kwargs['file_name'], k * dt) # Update the loop k += 1 # Update the flag for burn-time if max_burn_time: flag_burn = k * dt <= max_burn_time # ------------------------ POST-PROCESS ------------------------ # Convert to numpy arrays self.results['run_values'] = {key: asarray(value) for key, value in self.results["run_values"].items()} # Post-process the data self.post_process_data(dt=dt)
def run_simulation_constant_fuel_sliver(self, ox_flow, safety_thickness, dt, max_burn_time=None): """ Simulate the hybrid rocket burn. Every value is expressed in ISU unless specified otherwise. Simulation is only valid for normal ground atmosphere conditions (1 bar, 293 K). :param ox_flow: value of input oxidizer flow :param safety_thickness: minimum allowed thickness for fuel slivers, which conditions burn termination :param dt: time increment :return: TBD """ # Check the inputs assert ox_flow > 0, "Oxidizer flow not greater than 0, check your inputs to CombustionModule. \n" assert safety_thickness > 0, "Safety thickness not greater than 0, check your inputs to CombustionModule. \n" assert dt > 0, "Simulation time-increment not greater than 0, check your inputs to CombustionModule. \n" # Call for workspace definition methods g0, R, pression_atmo, a, n, m, rho_fuel, initial_chamber_pressure = self.unpack_data_dictionary() # -------------------- Initialize simulation_variables if required ------------- # regression_rate, fuel_flow, total_mass_flow, OF, T_chambre, gamma, masse_molaire_gaz, c_star = \ # self.initialize_variables() pression_chambre = initial_chamber_pressure # ---------------------------- MAIN SIMULATION LOOP ----------------------------- # Set a counter to keep-track of the loop k = 0 # Set the flag for the maximum burn-time flag_burn = True while self.geometry.min_bloc_thickness() > safety_thickness and flag_burn: # Regression rate and mass flow calculations fuel_flow = self.geometry.compute_fuel_rate(rho=rho_fuel, ox_flow=ox_flow, a=a, n=n, m=m) total_mass_flow = ox_flow + fuel_flow of_ratio = ox_flow / fuel_flow # Data extraction using the interpolator and OF ratio from already defined variables # Check lookup_from_cea method which variables have been chosen t_chamber, gamma, gas_molar_mass, cea_c_star = self.lookup_from_cea(desired_of_ratio=of_ratio) r = R / gas_molar_mass # Specific gaz constant # Calculate chamber conditions and motor performance # Chamber pressure is not recalculated as it is assumed constant mach_exit = iso.exit_mach_via_expansion(gamma=gamma, expansion=self.nozzle.get_expansion_ratio(), supersonic=True) exit_pressure = pression_chambre / iso.pressure_ratio(gamma, mach_exit) t_exit = t_chamber / iso.temperature_ratio(gamma, mach_exit) v_exit = math.sqrt(gamma * r * t_exit) * mach_exit thrust = self.nozzle.get_nozzle_effeciency() * (total_mass_flow*v_exit + (exit_pressure-pression_atmo) * self.nozzle.get_exit_area()) isp = thrust / total_mass_flow / g0 # Results updates self.results['run_values']['time'].append(k * dt) self.results["run_values"]["thrust"].append(thrust) self.results["run_values"]["isp"].append(isp) self.results["run_values"]["pressure"].append(pression_chambre) self.results["run_values"]["temperature"].append(t_chamber) self.results["run_values"]["of"].append(of_ratio) # Verify it is a single port_number before updating the port number if isinstance(self.geometry, OneCircularPort): self.results["run_values"]["radius"].append(self.geometry.get_port_radius()) else: # Append a 0 if there it is not a OneCircularPort geometry self.results["run_values"]["radius"].append(0) # Update the geometry and nozzle self.geometry.regress(ox_flow=ox_flow, a=a, n=n, m=m, dt=dt) self.nozzle.erode(dt) # Update the loop k += 1 # Update the flag for burn-time if max_burn_time: flag_burn = k * dt <= max_burn_time # ------------------------ POST-PROCESS ------------------------ # Convert to numpy arrays self.results['run_values'] = {key: asarray(value) for key, value in self.results["run_values"].items()} # Post-process the data self.post_process_data(dt=dt)
def run_simulation_constant_fuel_sliver(self, ox_flow, safety_thickness, dt, max_burn_time=None, **kwargs): """ Simulate the hybrid rocket burn. Every value is expressed in ISU unless specified otherwise. Simulation is only valid for normal ground atmosphere conditions (1 bar, 293 K). :param ox_flow: value of input oxidizer flow :param safety_thickness: minimum allowed thickness for fuel slivers, which conditions burn termination :param dt: time-step for the simulation :param max_burn_time: maximum burn time (if inputted) :return: TBD """ # Check the inputs assert ox_flow > 0, "Oxidizer flow not greater than 0, check your inputs to CombustionModule. \n" assert safety_thickness > 0, "Safety thickness not greater than 0, check your inputs to CombustionModule. \n" # Call for workspace definition methods g0, R, pression_atmo, a, n, m, rho_fuel, initial_chamber_pressure, eta_comb = self.unpack_data_dictionary() # -------------------- Initialize simulation_variables if required ------------- # regression_rate, fuel_flow, total_mass_flow, OF, T_chambre, gamma, masse_molaire_gaz, c_star = \ # self.initialize_variables() pression_chambre = initial_chamber_pressure # ---------------------------- MAIN SIMULATION LOOP ----------------------------- # Set the flag for the maximum burn-time flag_burn = True time = 0 # Get the thickness equivalent to 5 pixels dr = self.geometry.getMetersPerPixel() * 5 while self.geometry.min_bloc_thickness() > safety_thickness and flag_burn: # print( self.geometry.totalSurfaceArea() / self.geometry.get_length() / 2 / math.pi) # Regression rate and mass flow calculations fuel_flow = self.geometry.compute_fuel_rate(rho=rho_fuel, ox_flow=ox_flow) total_mass_flow = ox_flow + fuel_flow of_ratio = ox_flow / fuel_flow Go = ox_flow / self.geometry.totalCrossSectionArea() delta_t = dr / self.geometry.regressionModel.computeRegressionRate(self.geometry, ox_flow) # Call CEA process to obtain thermochemical variables t_chamber, gamma, gas_molar_mass, cea_c_star, son_vel, rho_ch = self.run_thermochemical_analysis( of_ratio, pression_chambre/10**5) r = R / gas_molar_mass # Specific gaz constant # Determine the flow port-speed u_ch = calculate_flow_speed(cross_section=self.geometry.totalCrossSectionArea(), mass_flow=total_mass_flow, density=rho_ch) # Calculate cea_c_star to consider the combustion efficiency parameter # cea_c_star = eta_comb * cea_c_star # Calculate chamber conditions and motor performance # Chamber pressure is not recalculated as it is assumed constant mach_exit = iso.exit_mach_via_expansion(gamma=gamma, expansion=self.nozzle.get_expansion_ratio(), supersonic=True) exit_pressure = pression_chambre / iso.pressure_ratio(gamma, mach_exit) t_exit = t_chamber / iso.temperature_ratio(gamma, mach_exit) v_exit = math.sqrt(gamma * r * t_exit) * mach_exit thrust = self.nozzle.get_nozzle_effeciency() * ( total_mass_flow * v_exit + (exit_pressure - pression_atmo) * self.nozzle.get_exit_area()) # Calculate the nozzle equation validation nozzle_p = total_mass_flow * cea_c_star / (self.nozzle.get_throat_area() * pression_chambre) # print(nozzle_p) isp = thrust / total_mass_flow / g0 # Results updates self.results['run_values']['time'].append(time) self.results["run_values"]["thrust"].append(thrust) self.results["run_values"]["isp"].append(isp) self.results["run_values"]["pressure"].append(pression_chambre) self.results["run_values"]["temperature"].append(t_chamber) self.results["run_values"]["of"].append(of_ratio) self.results["run_values"]["Go"].append(Go) self.results["run_values"]["nozzle_param"].append(nozzle_p) self.results["run_values"]["c_star"].append(cea_c_star) self.results["run_values"]["chamber_sound_speed"].append(son_vel) self.results["run_values"]["chamber_rho"].append(rho_ch) self.results["run_values"]["mass_flow"].append(total_mass_flow) self.results["run_values"]["mass_flow_ox"].append(ox_flow) self.results["run_values"]["mass_flow_f"].append(fuel_flow) self.results["run_values"]["chamber_speed"].append(u_ch) self.results["run_values"]["hydraulic_port_diameter"].append(self.geometry.get_hydraulic_diameter()) # Verify it is a single port_number before updating the port number if isinstance(self.geometry, (OneCircularPort, ThreeCircularPorts, MultipleCircularPortsWithCircularCenter)): self.results["run_values"]["radius"].append(self.geometry.get_port_radius()) # Compute regression rate self.results["run_values"]["regression_rate"].append(dr / delta_t) else: # Append a 0 if there it is not a OneCircularPort geometry self.results["run_values"]["radius"].append(nan) self.results["run_values"]["regression_rate"].append(nan) # Update the geometry and nozzle self.geometry.regress(ox_flow=ox_flow, dt=delta_t) self.nozzle.erode(delta_t) time += delta_t # Update the flag for burn-time if max_burn_time: flag_burn = time <= max_burn_time # ------------------------ POST-PROCESS ------------------------ # Convert to numpy arrays self.results['run_values'] = {key: asarray(value) for key, value in self.results["run_values"].items()} # Post-process the data self.post_process_data(dt=dt)