def create_struct(constraints, values: np.ndarray): goal['total_impulse'] = constraints[1] goal['max_thrust'] = constraints[2] goal['OF'] = constraints[3] goal['min_fuel_dp'] = constraints[4] goal['min_ox_to_dp'] = constraints[5] goal['ox_to_fuel_time'] = values[1] design['ox_ullage'] = constraints[6] design['p_tanks'] = values[2] design['exp_ratio'] = values[3] goal = Struct(goal) design = Struct(design) return goal, design
def design_liquid(initial_inputs: Struct, goal: Struct, design: Struct, output_on: bool): parameter_labels = [ 'Thrust', 'OF', 'Total Impulse', 'Min. Fuel Pressure Drop', 'Min. Ox. Pressure Drop', 'Ox. to Fuel Drain Time Ratio' ] paramater_field_names = goal.fieldnames() test_data = Struct() test_data.test_plots_on = 0 # Import tests data and plot against simulation data test_data.test_data_file = '' # File from which to import test data test_data.t_offset = 0 # Time offset of test data wrt simulation data [s] mode = Struct() mode.combustion_on = 1 mode.flight_on = 0 mode.type = 'liquid' max_iter = 100 param_tol = 0.005 # Enforce design parameters inputs = initial_inputs inputs.ox.T_tank = n2o_find_T(design.p_tanks) inputs.ox.V_tank = inputs.ox.V_l / (1 - design.ox_ullage) inputs.fuel_pressurant.set_pressure = design.p_tanks inputs.exp_ratio = design.exp_ratio
def n2o_properties(temp: int or float) -> Struct: """ Calculates an array of properties of nitrous oxide given a temperature in K. WARNING: if temperature input is outside of -90 to 30 C, properties will be generated for boundary (-90 C or 30 C). """ properties = dict( ) # Creates properties (output array) as a dictionary for which data from text file can be entered properties["Pvap"] = None # Nitrous vapor pressure properties["rho_l"] = None # Liquid density of nitrous properties["rho_g"] = None # Gas denisty of nitrous properties["deltaH_vap"] = None # Vaportization enthalpy properties["cp_l"] = None # Specific heat of liquid nitrous properties["cv_l"] = None # Specific volume of liquid nitrous properties["cp_g"] = None # Specific heat of gaseous nitrous properties["cv_g"] = None # Specific volume of gaseous nitrous properties["h_l"] = None # Enthalpy of liquid nitrous properties["h_g"] = None # Enthalpy of gaseous nitrous properties["s_l"] = None # Specific entropy of liquid nitrous properties["s_g"] = None # Specific entropy of gaseous nitrous properties["mu_l"] = None # Dynamic viscosity of nitrous liquid properties["mu_g"] = None # Dynamic viscosity of nitrous gas properties["e_l"] = None # Specific internal energy of liquid properties["e_g"] = None # Specific internal energy of gas R_u = 8.3144621 # Universal gas constant [J/mol*K] M_n2o = 0.044013 # Molecular mass of nitrous oxide [kg/mol] R_n2o_0 = R_u / M_n2o # Specific gas constant of nitrous oxide [J/kg*K] # Range-check temperature if temp < (-90 + 273.15): # If temperature is less that bottom bound temp = -90 + 273.150001 # Temperature is bottom bound for interpolate elif temp > (30 + 273.150001): # If temperature greater than top bound, temp = 30 + 273.150001 # Temperature equal to top bound for interpolate Tcrit = 309.57 # K Pcrit = 7251 # kPa rhocrit = 452 # kg/m^3 # Possibly add critical compressibility factor "Z" Tr = temp / Tcrit # Calculate vapor pressure, valid -90 to 36C b1 = -6.71893 b2 = 1.3596 b3 = -1.3779 b4 = -4.051 properties["Pvap"] = np.exp( (1 / Tr) * (b1 * (1 - Tr) + b2 * (1 - Tr)**(3 / 2) + b3 * (1 - Tr)**(5 / 2) + b4 * (1 - Tr)**5)) * Pcrit properties["Pvap"] = properties["Pvap"] * 1000 # Calculate Density of Liquid, valid -90C to 36C b1 = 1.72328 b2 = -0.83950 b3 = 0.51060 b4 = -0.10412 properties["rho_l"] = np.exp(b1 * (1 - Tr)**(1 / 3) + b2 * (1 - Tr)**(2 / 3) + b3 * (1 - Tr) + b4 * (1 - Tr)**(4 / 3)) * rhocrit # Calculate Density of Gas, valid -90C to 36C b1 = -1.00900 b2 = -6.28792 b3 = 7.50332 b4 = -7.90463 b5 = 0.629427 Trinv = 1. / Tr properties["rho_g"] = np.exp(b1 * (Trinv - 1)**(1 / 3) + b2 * (Trinv - 1)**(2 / 3) + b3 * (Trinv - 1) + b4 * (Trinv - 1)**(4 / 3) + b5 * (Trinv - 1)**(5 / 3)) * rhocrit # Calculate dynamic viscosity of saturated liquid, valid from -90C to 30C b1 = 1.6089 b2 = 2.0439 b3 = 5.24 b4 = 0.0293423 theta = (Tcrit - b3) / (temp - b3) properties["mu_l"] = b4 * np.exp(b1 * (theta - 1)**(1 / 3) + b2 * (theta - 1)**(4 / 3)) # Calculate dynamic viscosity of saturated vapor, valid from -90C to 30C b1 = 3.3281 b2 = -1.18237 b3 = -0.055155 Trinv = 1. / Tr properties["mu_g"] = np.exp(b1 + b2 * (Trinv - 1)**(1 / 3) + b3 * (Trinv - 1)**(4 / 3)) NIST_data = dict( ) # NIST_data is an array that stores variables regarding nitrous # Read each line in the N2O_Properties.cgi.txt document and enter each line into the dictionary, separated by tabs with open("./data/N2O_Properties.cgi.txt", "r") as reader: for x, line in enumerate(reader): if x == 0: temp_list = line.split("\t") arr = [list() for x in range(len(temp_list))] continue temp_list = line.split("\t") for i, val in enumerate(temp_list): arr[i].append(val) NIST_data["T"] = arr[0] NIST_data["h_liq"] = arr[5] NIST_data["h_gas"] = arr[17] NIST_data["e_liq"] = arr[4] NIST_data["e_gas"] = arr[16] NIST_data["cv_l"] = arr[7] NIST_data["cv_g"] = arr[19] NIST_data["s_l"] = arr[6] NIST_data["s_g"] = arr[18] NIST_data["cp_liq"] = arr[8] NIST_data["cp_gas"] = arr[20] NIST_data = { key: np.array(NIST_data[key], dtype="float64") for key in NIST_data } # Gas Specific Enthalpy properties["h_l"] = np.interp(temp, NIST_data["T"], NIST_data["h_liq"]) * 1000 # J/kg, properties["h_g"] = np.interp(temp, NIST_data["T"], NIST_data["h_gas"]) * 1000 # J/kg properties["e_l"] = np.interp(temp, NIST_data["T"], NIST_data["e_liq"]) * 1000 # J/kg properties["e_g"] = np.interp(temp, NIST_data["T"], NIST_data["e_gas"]) * 1000 # J/kg properties["deltaH_vap"] = properties["h_g"] - properties["h_l"] properties["deltaE_vap"] = properties["e_g"] - properties["e_l"] # Specific Heat at Constant Volume properties["cv_l"] = np.interp(temp, NIST_data["T"], NIST_data["cv_l"]) * 1000 properties["cv_g"] = np.interp(temp, NIST_data["T"], NIST_data["cv_g"]) * 1000 # Specific Heat at Constant Pressure properties["cp_l"] = np.interp(temp, NIST_data["T"], NIST_data["cp_liq"]) * 1000 properties["cp_g"] = np.interp(temp, NIST_data["T"], NIST_data["cp_gas"]) * 1000 # Specific Entropy properties["s_l"] = np.interp(temp, NIST_data["T"], NIST_data["s_l"]) * 1000 properties["s_g"] = np.interp(temp, NIST_data["T"], NIST_data["s_g"]) * 1000 # Convert Properties to Standard Units properties["mu_l"] = properties["mu_l"] * 10**-3 # mN*s/(m^2) -> N*s/m^2 properties["mu_g"] = properties["mu_g"] * 10**-6 # uN*s/(m^ 2)-> N*s/m^2 props = Struct( properties ) # Converts the output properties into standardized Struct type return props
def n2o_tank_mdot(inputs: Struct, state: LiquidStateVector, time: float) -> tuple: """ Calculates flow rate out of N2O Tank. Interpolates between liquid and gas flow based on amount of liquid oxidizer remaining. If no liquid oxidizer remains, it uses gas flow. If plenty of liquid oxidizer (i.e. mass greater than set tolerance), liquid flow is used. Linear interpolation is used (in order to avoid hysteresis) in between with a small, steady liquid source in tank. Inputs: - inputs: object representing motor inputs - state: object representing state of system - time: time Outputs: - m_dot_lox: mass flow rate out of tank of liquid oxidizer - m_dot_gox: mass flow rate out of tank of gaseous oxidizer - m_dot_oxtank_press: mass flow rate out of tank of pressurant gas - T_dot_drain: oxidizer tank temperature change from draining - p_crit: critical pressure below which draining flow is choked - m_dot_ox_crit: critical mass flow rate below which draining flow is choked """ # Constants dm_lox_tol = 1e-2 # The tolerance as mentioned above for "plenty" of liquid [kg] M_n2o = 0.044013 # Molecular mass of nitrous oxide [kg/mol] a_n2o = 0.38828/M_n2o^2 # van der Waal's constant a for N2O [[Pa*(kg/m^3)^-2]] inj_A_eff = inputs.ox.injector_area*inputs.throttle(time) # Effective area of the injector # If tank pressure is greater than combustion chamber pressure if state.p_oxmanifold > state.p_cc and inputs.ox.Cd_injector*inj_A_eff > 0: # Find flow rates for gas, flow rates for liquid, interpolate within tolerance for smooth transition m_dot_lox = np.zeros((2,1)) m_dot_gox = np.zeros((2,1)) m_dot_oxtank_press = np.zeros((2,1)) p_crit = np.zeros((2,1)) m_dot_ox_crit = np.zeros((2,1)) Q = np.zeros((2,1)) # Volumetric flow rate # Liquid flow m_dot_ox, m_dot_ox_crit[0], p_crit[0] = liq_n2o_mdot(inputs, inj_A_eff, state.p_oxmanifold, state.T_oxtank, state.p_cc) m_dot_lox[0] = m_dot_ox m_dot_gox[0] = 0 m_dot_oxtank_press[1] = 0 Q[1] = m_dot_ox/state.n2o_props.rho_l # Gas Flow d_inj = np.sqrt(4/np.pi*inj_A_eff) _, _, _, _, m_dot_ox, _ = nozzle_calc( d_inj, d_inj, state.T_oxtank, \ state.p_oxmanifold, state.gamma_ox_ullage, M_n2o, state.p_cc) p_crit[1] = 0 m_dot_ox_crit[1] = 0 m_dot_lox[1] = 0 m_dot_gox[1] = m_dot_ox*\ (state.m_gox)/(state.m_gox + state.m_oxtank_press) m_dot_oxtank_press[1] = m_dot_ox*\ (state.m_oxtank_press)/(state.m_gox + state.m_oxtank_press) Q[1] = m_dot_ox*state.V_ox_ullage/(state.m_gox + state.m_oxtank_press) # Total Flow Rate if state.m_lox > dm_lox_tol: frac_lox = 1 else: frac_lox = max(0,state.m_lox/dm_lox_tol) m_dot_lox = frac_lox*m_dot_lox(1) + (1-frac_lox)*m_dot_lox[1] m_dot_gox = frac_lox*m_dot_gox(1) + (1-frac_lox)*m_dot_gox[1] m_dot_oxtank_press = frac_lox*m_dot_oxtank_press(1) + (1-frac_lox)*m_dot_oxtank_press[1] p_crit = frac_lox*p_crit(1) + (1-frac_lox)*p_crit[1] m_dot_ox_crit = frac_lox*m_dot_ox_crit(1) + (1-frac_lox)*m_dot_ox_crit[1] Q = frac_lox*Q[0] + (1-frac_lox)*Q[1] else: # bruh ur bad if ur chamber pressure is greater than tank press m_dot_lox = 0 m_dot_gox = 0 m_dot_oxtank_press = 0 p_crit = state.p_oxtank m_dot_ox_crit = 0 Q = 0
def integration(inputs: Struct) -> Tuple[float, Struct]: """ Integrate necesary differential equations for rocket engine modeling using Euler's method. TODO: Include real combustion properties and supercharging. INPUTS: - inputs: structure of motor characteristics (all units SI: m, s, kg, K, mol) - CombustionData: string filename in "Combustion Data/" folder from which to source combustion data - Injexit_area: total orifice area of injector - Cdischarge: discharge coefficient of injector orifices - MOVTime: time for injector flow to ramp up to 100 - rocket_dry_mass: dry mass of rocket - tankvol: volume of oxidizer tank - l_vol: initial volume of liquid nitrous oxide in oxidizer tank - tank_id: inner diameter of tank - h_offset: height difference between bottom of tank and injector - flowline_id: inner diameter of flow line - Ttank: tank initial temperature - Fueldensity: density of fuel - grainlength: length of fuel grain - graindiameter: outer diameter of grain - portradius0: grain intial port radius - chamberlength: combustion chamber total length - n: grain ballistic coefficient (r_dot = a*G^n) - a: grain ballistic coefficient (r_dot = a*G^n) - nozzle_efficiency: nozzle exhaust energy efficiency (proportion) - nozzle_correction_factor: proportion of ideal thrust (factor for divergence losses, etc.) actually achieved - c_star_efficiency: combustion efficiency / proportion of c* actually achieved - Tdiameter: nozzle throat diameter - E: expansion ratio - Tamb: ambient temperature - Pamb: ambient pressure - SPress: supercharging regulator pressure - M_sc: molecular mass - SVol: volume of external pressurization tank (0 for supercharging / no external tank) - c_v_S: specific heat at constant volume of pressurant gas - c_p_S: specific heat at constant volume of pressurant gas - P_S_i: initial pressurant gas storage pressure (must be present, but not used for supercharging / no external tank) - S_CdA: effective flow area of pressurant gas (must be but not used for supercharging / no external tank) - Charged: 1 for pressurant gas present, 0 for no pressurant gas present - mode: structure of options defining mode of motor operation - combustion_on: 1 for hot-fire, 0 for cold-flow - flight_on: 1 for flight conditions, 0 for ground conditions - tspan: vector of time values over which to record outputs OUTPUS: - tspan: output time vector - F_thrust: thrust over tspan - p_cc: combustion chamber pressure over tspan - p_oxtank: tank pressure over tspan - p_oxmanifold: oxidizer manifold pressure over tspan - T_tank: tank temperature over tspan - T_cc: combustion chamber as temperature over tspan - area_core: fuel grain core area over tspan - OF: oxidizer/fuel ratio over tspan - m_dot_ox: oxidizer mass flow rate over tspan - m_dot_ox_crit: critical two-phase oxidizer mass flow rate over tspan - p_crit: two-phase critical downstream pressure over tspan - M_e: exit Mach number over tspan - p_exit: nozzle exit pressure over tspan - p_shock: critical back pressure for normal shock formation over tspan """ # Recording the output data for this timestep record_config = { "F_thrust" : None, "p_cc" : None, "p_oxtank" : None, "p_oxpresstank" : None, "p_fueltank" : None, "p_fuelpresstank" : None, "p_oxmanifold" : None, "T_oxtank" : None, "T_cc" : None, "area_core" : None, "of_ratio_i" : None, "gamma_ex" : None, "m_dot_ox" : None, "m_dot_fuel" : None, "p_crit" : None, "m_dot_ox_crit" : None, "M_e" : None, "p_exit" : None, "p_shock" : None } record = Struct(record_config) # Convert into Struct class state_0, x0 = init_liquid_state(inputs) # Configure the integrator solve_ivp(liquid_model, method='BDF') # NOTE: Incomplete method call
def n2o_properties(temp: int or float) -> Struct: """ Calculates properties of nitrous oxide given a temperature in K WARNING: if temperature input is outside of -90 to 30 C, properties will be generated for boundary (-90 C or 30 C) """ properties = Struct() properties.Pvap = None properties.rho_l = None properties.rho_g = None properties.deltaH_vap = None properties.cp_l = None properties.cv_l = None properties.cp_g = None properties.cv_g = None properties.h_l = None properties.h_g = None properties.s_l = None properties.s_g = None properties.mu_l = None properties.mu_g = None # Returns properties (structure) # properties.Pvap in Pa # properties.rho_l in kg/m^3 # properties.rho_g in kg/m^3 # properties.deltaH_vap in J/kg # properties.cp_l in J/(kg*K) # properties.cv_l in J/(kg*K) # properties.cp_g in J/(kg*K) # properties.cv_g in J/(kg*K) # properties.h_l in J/(kg*K) # properties.h_g in J/(kg*K) # properties.s_l in J/(kg*K) # properties.s_g in J/(kg*K) # properties.mu_l in N*s/(m^2) # properties.mu_g in N*s/(m^2) NIST_DATA = Struct() # NIST_data is an array that stores variables regarding nitrous R_u = 8.3144621 # Universal gas constant [J/mol*K] M_n2o = 0.044013 # Molecular mass of nitrous oxide [kg/mol] R_n2o_0 = R_u/M_n2o # Specific gas constant of nitrous oxide [J/kg*K] # Range-check temperature if temp < (-90 + 273.15): temp = -90 + 273.150001 elif temp > (30 + 273.150001): temp = 30 + 273.150001 Tcrit = 309.57 #K Pcrit = 7251 #kPa rhocrit = 452 #kg/m^3 #possibly add critical compressibility factor "Z" Tr = temp/Tcrit # Calculate vapor pressure, valid -90 to 36C b1 = -6.71893 b2 = 1.3596 b3 = -1.3779 b4 = -4.051 properties.Pvap = exp((1./Tr).*(b1*(1-Tr) + b2*(1-Tr).^(3/2) + b3*(1-Tr).^(5/2) + b4*(1-Tr).^5))*Pcrit properties.Pvap = properties.Pvap*1000 # Calculate Density of Liquid, valid -90C to 36C b1 = 1.72328 b2 = -0.83950 b3 = 0.51060 b4 = -0.10412 properties.rho_l = exp(b1*(1-Tr).^(1/3) + b2*(1-Tr).^(2/3) + b3*(1-Tr) + b4*(1-Tr).^(4/3))*rhocrit # Calculate Density of Gas, valid -90C to 36C b1 = -1.00900 b2 = -6.28792 b3 = 7.50332 b4 = -7.90463 b5 = 0.629427 Trinv = 1./Tr properties.rho_g = exp(b1*(Trinv-1).^(1/3) + b2*(Trinv-1).^(2/3) + b3*(Trinv-1) + b4*(Trinv-1).^(4/3) + b5*(Trinv-1).^(5/3))*rhocrit # Calculate dynamic viscosity of saturated liquid, valid from -90C to 30C b1 = 1.6089 b2 = 2.0439 b3 = 5.24 b4 = 0.0293423 theta = (Tcrit-b3)./(temp-b3) properties.mu_l = b4*exp(b1*(theta-1).^(1/3) + b2*(theta-1).^(4/3)) # Calculate dynamic viscosity of saturated vapor, valid from -90C to 30C b1 = 3.3281 b2 = -1.18237 b3 = -0.055155 Trinv = 1./Tr properties.mu_g = exp(b1 + b2*(Trinv-1).^(1/3) + b3*(Trinv-1).^(4/3)) # create dict that uses temp value as key and array of remaining values as the return # Find Specific Enthalpy reader = open("N2O_Properties.cgi.txt", 'r') try: reader.readline() tempList = reader.readline().split("\t") dictionary = {tempList.pop(0):tempList} for x in range(0,125): tempList = reader.readline().split("\t") dictionary[tempList.pop(0)] = tempList finally: reader.close() if isempty(NIST_data): data = tdfread('N2O_Properties.cgi.txt') NIST_data.T = data.Temperature_0x28K0x29 NIST_data.h_liq = data.Enthalpy_0x28l0x2C_kJ0x2Fkg0x29 NIST_data.h_gas = data.Enthalpy_0x28v0x2C_kJ0x2Fkg0x29 NIST_data.e_liq = data.Internal_Energy_0x28l0x2C_kJ0x2Fkg0x29 NIST_data.e_gas = data.Internal_Energy_0x28v0x2C_kJ0x2Fkg0x29 NIST_data.cv_l = data.Cv_0x28l0x2C_J0x2Fg0x2AK0x29 # Cv for liquid NIST_data.cv_g = data.Cv_0x28v0x2C_J0x2Fg0x2AK0x29 # Cv for gas NIST_data.cp_l = data.Cp_0x28l0x2C_J0x2Fg0x2AK0x29 # Cp for liquid NIST_data.cp_g = data.Cp_0x28v0x2C_J0x2Fg0x2AK0x29 # Cp for gas NIST_data.s_l = data.Entropy_0x28l0x2C_J0x2Fg0x2AK0x29 NIST_data.s_g = data.Entropy_0x28v0x2C_J0x2Fg0x2AK0x29 # Gas Specific Enthalpy properties.h_l = FastInterp1(NIST_data.T, NIST_data.h_liq, temp)*1000 # J/kg, properties.h_g = FastInterp1(NIST_data.T, NIST_data.h_gas, temp)*1000 # J/kg properties.e_l = FastInterp1(NIST_data.T, NIST_data.e_liq, temp)*1000 # J/kg properties.e_g = FastInterp1(NIST_data.T, NIST_data.e_gas, temp)*1000 # J/kg properties.deltaH_vap = properties.h_g-properties.h_l properties.deltaE_vap = properties.e_g-properties.e_l # Specific Heat at Constant Volume properties.cv_l = FastInterp1(NIST_data.T, NIST_data.cv_l, temp)*1000 properties.cv_g = FastInterp1(NIST_data.T, NIST_data.cv_g, temp)*1000 # Specific Heat at Constant Pressure properties.cp_l = FastInterp1(NIST_data.T, NIST_data.cp_l, temp)*1000 properties.cp_g = FastInterp1(NIST_data.T, NIST_data.cp_g, temp)*1000 #Specific Entropy properties.s_l = FastInterp1(NIST_data.T, NIST_data.s_l, temp)*1000 properties.s_g = FastInterp1(NIST_data.T, NIST_data.s_g, temp)*1000 ## Convert Properties to Standard Units properties.mu_l = properties.mu_l*10^-3 # mN*s/(m^2) -> N*s/m^2 properties.mu_g = properties.mu_g*10^-6 # uN*s/(m^2) -> N*s/m^2 return properties
def integration(inputs: Inputs, options: Struct) -> Tuple[float, Struct]: record = Struct() # Recording the output data for this timestep state_0, x0 = InitializeLiquidState(inputs, mode) pass
#----------Unit Conversions------------- psi_to_Pa = 6894.75729 # 1 psi in Pa in_to_m = 0.0254 # 1 in in m mm_to_m = 1e-3 # 1 mm in m lbf_to_N = 4.44822162 # 1 lbf in N lbm_to_kg = 0.453592 # 1 lbm in kg atm_to_Pa = 101325 # 1 atm in Pa L_to_m3 = 1e-3 # 1 L in m^3 #-------Gas Properties---------- nitrogen = Gas() nitrogen.c_v = 0.743e3 # J/kg*K nitrogen.molecular_mass = 2 * 14.0067e-3 # kg/mol inputs = Inputs() options = Struct() options.t_final = 60 # sec options.dt = 0.01 # sec options.output_on = True # t_final: simulation time limit # dt: output time resolution # output_on: true for plots, false for no plots #-------Injector Properties---------- # Injector Exit Area inputs.ox.injector_area = 2.571e-05 # m^2 inputs.fuel.injector_area = 6.545e-06 # 4.571e-06 m^2 # Ball Valve Time to Injector Area (s) inputs.dt_valve_open = 0.01